Files
Memoh/cmd/cli/main.go
T
2026-02-08 21:39:34 +08:00

258 lines
5.2 KiB
Go

package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"time"
gocni "github.com/containerd/go-cni"
)
func main() {
flag.CommandLine.SetOutput(io.Discard)
containerID := flag.String("container-id", "", "")
flag.Parse()
if len(flag.Args()) > 0 {
switch flag.Arg(0) {
case "cni-setup":
os.Exit(runCNISetup(flag.Args()[1:]))
case "cni-remove":
os.Exit(runCNIRemove(flag.Args()[1:]))
case "cni-check":
os.Exit(runCNICheck(flag.Args()[1:]))
case "cni-status":
os.Exit(runCNIStatus(flag.Args()[1:]))
}
}
if *containerID == "" {
os.Exit(2)
}
cmd := buildMCPCommand(*containerID)
if err := runWithStdio(cmd); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
os.Exit(exitErr.ExitCode())
}
os.Exit(1)
}
}
func buildMCPCommand(containerID string) *exec.Cmd {
execID := "mcp-" + strconv.FormatInt(time.Now().UnixNano(), 10)
if runtime.GOOS == "darwin" {
return exec.Command(
"limactl",
"shell",
"--tty=false",
"default",
"--",
"sudo",
"-n",
"ctr",
"-n",
"default",
"tasks",
"exec",
"--exec-id",
execID,
containerID,
"/mcp",
)
}
return exec.Command(
"ctr",
"-n",
"default",
"tasks",
"exec",
"--exec-id",
execID,
containerID,
"/mcp",
)
}
func runWithStdio(cmd *exec.Cmd) error {
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
_ = stdin.Close()
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
_ = stdin.Close()
_ = stdout.Close()
return err
}
if err := cmd.Start(); err != nil {
_ = stdin.Close()
_ = stdout.Close()
_ = stderr.Close()
return err
}
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
_, _ = io.Copy(stdin, os.Stdin)
_ = stdin.Close()
}()
go func() {
defer wg.Done()
_, _ = io.Copy(os.Stdout, stdout)
}()
go func() {
defer wg.Done()
_, _ = io.Copy(os.Stderr, stderr)
}()
err = cmd.Wait()
wg.Wait()
return err
}
func runCNISetup(args []string) int {
id, netns, err := parseCNIArgs(args)
if err != nil {
return exitWithError(err)
}
cni, err := newCNIFromArgs(args)
if err != nil {
return exitWithError(err)
}
if err := cni.Load(gocni.WithLoNetwork, gocni.WithDefaultConf); err != nil {
return exitWithError(err)
}
result, err := cni.Setup(context.Background(), id, netns)
if err != nil {
return exitWithError(err)
}
if result != nil {
_ = json.NewEncoder(os.Stdout).Encode(result)
}
return 0
}
func runCNIRemove(args []string) int {
id, netns, err := parseCNIArgs(args)
if err != nil {
return exitWithError(err)
}
cni, err := newCNIFromArgs(args)
if err != nil {
return exitWithError(err)
}
if err := cni.Load(gocni.WithLoNetwork, gocni.WithDefaultConf); err != nil {
return exitWithError(err)
}
if err := cni.Remove(context.Background(), id, netns); err != nil {
return exitWithError(err)
}
return 0
}
func runCNICheck(args []string) int {
id, netns, err := parseCNIArgs(args)
if err != nil {
return exitWithError(err)
}
cni, err := newCNIFromArgs(args)
if err != nil {
return exitWithError(err)
}
if err := cni.Load(gocni.WithLoNetwork, gocni.WithDefaultConf); err != nil {
return exitWithError(err)
}
if err := cni.Check(context.Background(), id, netns); err != nil {
return exitWithError(err)
}
return 0
}
func runCNIStatus(args []string) int {
cni, err := newCNIFromArgs(args)
if err != nil {
return exitWithError(err)
}
if err := cni.Load(gocni.WithLoNetwork, gocni.WithDefaultConf); err != nil {
return exitWithError(err)
}
if err := cni.Status(); err != nil {
return exitWithError(err)
}
return 0
}
func parseCNIArgs(args []string) (string, string, error) {
fs := flag.NewFlagSet("cni", flag.ContinueOnError)
fs.SetOutput(io.Discard)
id := fs.String("id", "", "")
netns := fs.String("netns", "", "")
pid := fs.Int("pid", 0, "")
_ = fs.String("conf-dir", "", "")
_ = fs.String("bin-dir", "", "")
_ = fs.String("if-prefix", "", "")
if err := fs.Parse(args); err != nil {
return "", "", err
}
if *id == "" {
return "", "", fmt.Errorf("missing --id")
}
if *netns == "" && *pid == 0 {
return "", "", fmt.Errorf("missing --netns or --pid")
}
if *netns == "" {
*netns = filepath.Join("/proc", strconv.Itoa(*pid), "ns", "net")
}
return *id, *netns, nil
}
func newCNIFromArgs(args []string) (gocni.CNI, error) {
fs := flag.NewFlagSet("cni", flag.ContinueOnError)
fs.SetOutput(io.Discard)
confDir := fs.String("conf-dir", "", "")
binDir := fs.String("bin-dir", "", "")
ifPrefix := fs.String("if-prefix", "", "")
_ = fs.String("id", "", "")
_ = fs.String("netns", "", "")
_ = fs.Int("pid", 0, "")
if err := fs.Parse(args); err != nil {
return nil, err
}
opts := []gocni.Opt{}
if strings.TrimSpace(*binDir) != "" {
opts = append(opts, gocni.WithPluginDir([]string{*binDir}))
}
if strings.TrimSpace(*confDir) != "" {
opts = append(opts, gocni.WithPluginConfDir(*confDir))
}
if strings.TrimSpace(*ifPrefix) != "" {
opts = append(opts, gocni.WithInterfacePrefix(*ifPrefix))
}
return gocni.New(opts...)
}
func exitWithError(err error) int {
_, _ = fmt.Fprintln(os.Stderr, err.Error())
return 1
}