Skip to content

Commit 01b21a1

Browse files
authored
Merge pull request #453 from frobware/bpfman-crictl
Replace external crictl dependency with native bpfman-crictl implementation
2 parents 84dcde5 + 9eaf941 commit 01b21a1

File tree

50 files changed

+61872
-2973
lines changed

Some content is hidden

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

50 files changed

+61872
-2973
lines changed

Containerfile.bpfman-agent

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,7 @@ COPY . .
2424
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
2525
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -mod vendor -o bpfman-agent ./cmd/bpfman-agent/main.go
2626
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -mod vendor -o metrics-proxy ./cmd/metrics-proxy/main.go
27-
28-
29-
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.24 AS cri-tools-build
30-
# The following ARGs are set internally by docker/build-push-action in github actions
31-
ARG TARGETOS
32-
ARG TARGETARCH
33-
ARG TARGETPLATFORM
34-
35-
ARG BUILDPLATFORM
36-
37-
RUN echo "TARGETOS=${TARGETOS} TARGETARCH=${TARGETARCH} BUILDPLATFORM=${BUILDPLATFORM} TARGETPLATFORM=${TARGETPLATFORM}"
38-
39-
WORKDIR /usr/src/cri-tools
40-
ARG CRI_REPO_URL=https://github.com/kubernetes-sigs/cri-tools
41-
ARG CRI_REPO_BRANCH=master
42-
43-
RUN git clone --depth 1 --branch $CRI_REPO_BRANCH $CRI_REPO_URL .
44-
45-
# Build
46-
RUN GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} VERSION="latest" make
47-
RUN cp ./build/bin/${TARGETOS}/${TARGETARCH}/crictl .
27+
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -mod vendor -o bpfman-crictl ./cmd/bpfman-crictl/main.go
4828

4929
# Use the fedora minimal image to reduce the size of the final image but still
5030
# be able to easily install extra packages.
@@ -61,9 +41,6 @@ ARG TARGETPLATFORM
6141
WORKDIR /
6242
COPY --from=bpfman-agent-build /usr/src/bpfman-operator/bpfman-agent .
6343
COPY --from=bpfman-agent-build /usr/src/bpfman-operator/metrics-proxy .
64-
65-
# Install crictl
66-
COPY --from=cri-tools-build /usr/src/cri-tools/crictl /usr/local/bin
67-
RUN chmod +x /usr/local/bin/crictl
44+
COPY --from=bpfman-agent-build /usr/src/bpfman-operator/bpfman-crictl .
6845

6946
ENTRYPOINT ["/bpfman-agent"]

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,10 +340,11 @@ build-release-yamls: generate kustomize ## Generate the crd install bundle for a
340340
##@ Build
341341

342342
.PHONY: build
343-
build: fmt ## Build bpfman-operator and bpfman-agent binaries.
343+
build: fmt ## Build bpfman-operator, bpfman-agent, and bpfman-crictl binaries.
344344
CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -mod vendor -o bin/bpfman-operator cmd/bpfman-operator/main.go
345345
CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -mod vendor -o bin/bpfman-agent cmd/bpfman-agent/main.go
346346
CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -mod vendor -o bin/metrics-proxy cmd/metrics-proxy/main.go
347+
CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -mod vendor -o bin/bpfman-crictl cmd/bpfman-crictl/main.go
347348

348349
# These paths map the host's GOCACHE location to the container's
349350
# location. We want to mount the host's Go cache in the container to

cmd/bpfman-crictl/main.go

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
Copyright 2025 The bpfman Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package main implements bpfman-crictl, a minimal crictl replacement
18+
// for bpfman-operator.
19+
//
20+
// This is a cut-down version of crictl that implements only the
21+
// specific functionality needed by bpfman-operator for container PID
22+
// discovery:
23+
//
24+
// - pods --name <name> -o json (list pods by exact name)
25+
// - ps --pod <pod-id> -o json (list containers in a pod)
26+
// - inspect -o json <container-id> (inspect container for PID)
27+
//
28+
// Unlike the full crictl implementation, this version:
29+
//
30+
// - Uses exact string matching for pod names instead of regex matching
31+
// - Only supports JSON output format
32+
// - Implements timeout-based socket discovery matching crictl's behavior
33+
// - Provides a native Go CRI gRPC client instead of external process calls
34+
//
35+
// This eliminates the external crictl dependency while maintaining
36+
// functionality for bpfman-operator's container discovery use case.
37+
package main
38+
39+
import (
40+
"context"
41+
"encoding/json"
42+
"fmt"
43+
"os"
44+
"time"
45+
46+
"github.com/bpfman/bpfman-operator/pkg/crictl"
47+
)
48+
49+
func main() {
50+
if len(os.Args) < 2 {
51+
fmt.Fprintf(os.Stderr, "Usage: %s <command> [args...]\n", os.Args[0])
52+
fmt.Fprintf(os.Stderr, "Commands:\n")
53+
fmt.Fprintf(os.Stderr, " pods --name <name> -o json List pods\n")
54+
fmt.Fprintf(os.Stderr, " ps --pod <pod-id> -o json List containers\n")
55+
fmt.Fprintf(os.Stderr, " inspect -o json <container-id> Inspect container\n")
56+
os.Exit(1)
57+
}
58+
59+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
60+
defer cancel()
61+
62+
client, err := crictl.NewClient(ctx)
63+
if err != nil {
64+
fmt.Fprintf(os.Stderr, "Error creating CRI client: %v\n", err)
65+
os.Exit(1)
66+
}
67+
defer client.Close()
68+
69+
command := os.Args[1]
70+
args := os.Args[2:]
71+
72+
var output string
73+
switch command {
74+
case "pods":
75+
output, err = handlePodsCommand(client, args)
76+
case "ps":
77+
output, err = handlePsCommand(client, args)
78+
case "inspect":
79+
output, err = handleInspectCommand(client, args)
80+
default:
81+
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
82+
os.Exit(1)
83+
}
84+
85+
if err != nil {
86+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
87+
os.Exit(1)
88+
}
89+
90+
fmt.Println(output)
91+
}
92+
93+
func handlePodsCommand(client *crictl.Client, args []string) (string, error) {
94+
var nameFilter string
95+
var outputJSON bool
96+
97+
// Parse arguments.
98+
for i := 0; i < len(args); i++ {
99+
switch args[i] {
100+
case "--name":
101+
if i+1 < len(args) {
102+
nameFilter = args[i+1]
103+
i++ // Skip next arg
104+
}
105+
case "-o":
106+
if i+1 < len(args) && args[i+1] == "json" {
107+
outputJSON = true
108+
i++ // Skip next arg
109+
}
110+
}
111+
}
112+
113+
if !outputJSON {
114+
return "", fmt.Errorf("only JSON output is supported")
115+
}
116+
117+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
118+
defer cancel()
119+
120+
resp, err := client.ListPods(ctx, nameFilter)
121+
if err != nil {
122+
return "", fmt.Errorf("listing pods: %w", err)
123+
}
124+
125+
jsonOutput, err := json.MarshalIndent(resp, "", " ")
126+
if err != nil {
127+
return "", fmt.Errorf("marshaling JSON: %w", err)
128+
}
129+
130+
return string(jsonOutput), nil
131+
}
132+
133+
func handlePsCommand(client *crictl.Client, args []string) (string, error) {
134+
var podID string
135+
var outputJSON bool
136+
137+
// Parse arguments.
138+
for i := 0; i < len(args); i++ {
139+
switch args[i] {
140+
case "--pod":
141+
if i+1 < len(args) {
142+
podID = args[i+1]
143+
i++ // Skip next arg
144+
}
145+
case "-o":
146+
if i+1 < len(args) && args[i+1] == "json" {
147+
outputJSON = true
148+
i++ // Skip next arg
149+
}
150+
}
151+
}
152+
153+
if !outputJSON {
154+
return "", fmt.Errorf("only JSON output is supported")
155+
}
156+
157+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
158+
defer cancel()
159+
160+
resp, err := client.ListContainers(ctx, podID)
161+
if err != nil {
162+
return "", fmt.Errorf("listing containers: %w", err)
163+
}
164+
165+
jsonOutput, err := json.MarshalIndent(resp, "", " ")
166+
if err != nil {
167+
return "", fmt.Errorf("marshaling JSON: %w", err)
168+
}
169+
170+
return string(jsonOutput), nil
171+
}
172+
173+
func handleInspectCommand(client *crictl.Client, args []string) (string, error) {
174+
var containerID string
175+
var outputJSON bool
176+
177+
// Parse arguments.
178+
for i := 0; i < len(args); i++ {
179+
switch args[i] {
180+
case "-o":
181+
if i+1 < len(args) && args[i+1] == "json" {
182+
outputJSON = true
183+
i++ // Skip next arg
184+
}
185+
default:
186+
// Assume it's the container ID if it doesn't start with -
187+
if args[i][0] != '-' {
188+
containerID = args[i]
189+
}
190+
}
191+
}
192+
193+
if !outputJSON {
194+
return "", fmt.Errorf("only JSON output is supported")
195+
}
196+
197+
if containerID == "" {
198+
return "", fmt.Errorf("container ID is required")
199+
}
200+
201+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
202+
defer cancel()
203+
204+
resp, err := client.InspectContainer(ctx, containerID)
205+
if err != nil {
206+
return "", fmt.Errorf("inspecting container: %w", err)
207+
}
208+
209+
jsonOutput, err := json.MarshalIndent(resp, "", " ")
210+
if err != nil {
211+
return "", fmt.Errorf("marshaling JSON: %w", err)
212+
}
213+
214+
return string(jsonOutput), nil
215+
}

0 commit comments

Comments
 (0)