从Bash渐进式迁移到Golang命令行API开发指南

从Bash渐进式迁移到Golang命令行API开发指南 https://github.com/containers/toolbox 是一个非常实用的 Bash 脚本,它可以启动一个非特权 podman 容器,以便安全地安装库和修改系统文件,而不会影响父系统。

然而,该工具的开发已经停滞,因为项目正在进行一次用 Go 语言的完全重写 - https://github.com/containers/toolbox/pull/318

作为一名用户,我觉得这种策略令人沮丧。我希望在 Bash 工具的某些部分被重写并移植到 Go 语言的过程中,能够继续使用和修补这个 Bash 工具。有些错误只在 Go 分支中得到修复,并且由于 Go 分支使用 bash 测试来保持一致性,那里的 bash 代码也包含了修复。但它并没有发布。

想法

一种从 bash 中枚举和调用 go 函数的标准方法。

goprog apilist                 - 获取 goprog 中的函数列表
goprog apicheck <function>     - 检查函数是否存在
goprog apihelp <function>      - 获取函数参数和描述
goprog apicall <function> [param1] [param2]

然后在 bash 中检查函数列表,并将现有的 bash 函数重定向到 goprog 中的 apicall。

这可能吗?


更多关于从Bash渐进式迁移到Golang命令行API开发指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

abitrolly:

一种从Bash中枚举和调用go函数的标准方法。

abitrolly:

这可行吗?

这本质上是在一个独立的Github仓库中编写你自己的名为goprog的Go程序。因此,在你这边是可以实现的。

至于CLI接口,你可以查看 GitHub - spf13/cobra: 现代Go CLI交互的指挥官GitHub - spf13/viper: 带尖牙的Go配置管理

祝你Go之旅愉快。

abitrolly:

作为一名用户,我发现这种策略令人沮丧。我希望在工具的某些部分被重写并移植到Go的同时,能够继续使用和修补Bash工具。有些bug只在Go分支中修复了,并且因为Go分支使用Bash测试来保持一致性,那里的Bash代码也包含了修复。但它没有被发布。

问题的核心在于工具箱设计的范围和方向。你可能需要将这个问题反馈回那个Github工具箱的问题工单。

如果Go程序被正确实现,你就不应该在其开发和生产操作(包括单元测试)中使用shell脚本包装器。

更多关于从Bash渐进式迁移到Golang命令行API开发指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从Bash渐进式迁移到Go语言命令行API开发是可行的技术方案。以下是实现你描述的标准API调用方法的Go代码示例:

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"
)

// API函数注册表
var apiFunctions = map[string]func([]string) error{
	"listContainers":   listContainers,
	"createContainer":  createContainer,
	"removeContainer":  removeContainer,
	"executeCommand":   executeCommand,
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: goprog <command> [args...]")
		os.Exit(1)
	}

	command := os.Args[1]
	args := os.Args[2:]

	switch command {
	case "apilist":
		apiList()
	case "apicheck":
		if len(args) < 1 {
			fmt.Println("Usage: goprog apicheck <function>")
			os.Exit(1)
		}
		apiCheck(args[0])
	case "apihelp":
		if len(args) < 1 {
			fmt.Println("Usage: goprog apihelp <function>")
			os.Exit(1)
		}
		apiHelp(args[0])
	case "apicall":
		if len(args) < 1 {
			fmt.Println("Usage: goprog apicall <function> [params...]")
			os.Exit(1)
		}
		apiCall(args[0], args[1:])
	default:
		fmt.Printf("Unknown command: %s\n", command)
		os.Exit(1)
	}
}

func apiList() {
	funcList := make([]string, 0, len(apiFunctions))
	for name := range apiFunctions {
		funcList = append(funcList, name)
	}
	
	output, _ := json.Marshal(funcList)
	fmt.Println(string(output))
}

func apiCheck(function string) {
	_, exists := apiFunctions[function]
	fmt.Println(exists)
}

func apiHelp(function string) {
	helpTexts := map[string]string{
		"listContainers":  "listContainers [filter] - 列出容器,可选过滤条件",
		"createContainer": "createContainer <name> <image> - 创建新容器",
		"removeContainer": "removeContainer <name> [force] - 删除容器",
		"executeCommand":  "executeCommand <container> <command> - 在容器内执行命令",
	}
	
	if help, exists := helpTexts[function]; exists {
		fmt.Println(help)
	} else {
		fmt.Printf("No help available for function: %s\n", function)
	}
}

func apiCall(function string, params []string) {
	if fn, exists := apiFunctions[function]; exists {
		err := fn(params)
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			os.Exit(1)
		}
	} else {
		fmt.Printf("Function not found: %s\n", function)
		os.Exit(1)
	}
}

// 实际的API函数实现
func listContainers(args []string) error {
	filter := ""
	if len(args) > 0 {
		filter = args[0]
	}
	
	// 这里实现实际的容器列出逻辑
	fmt.Printf("Listing containers with filter: %s\n", filter)
	return nil
}

func createContainer(args []string) error {
	if len(args) < 2 {
		return fmt.Errorf("createContainer requires name and image parameters")
	}
	
	name := args[0]
	image := args[1]
	
	// 这里实现实际的容器创建逻辑
	fmt.Printf("Creating container %s from image %s\n", name, image)
	return nil
}

func removeContainer(args []string) error {
	if len(args) < 1 {
		return fmt.Errorf("removeContainer requires container name")
	}
	
	name := args[0]
	force := false
	if len(args) > 1 && strings.ToLower(args[1]) == "true" {
		force = true
	}
	
	// 这里实现实际的容器删除逻辑
	fmt.Printf("Removing container %s (force: %v)\n", name, force)
	return nil
}

func executeCommand(args []string) error {
	if len(args) < 2 {
		return fmt.Errorf("executeCommand requires container name and command")
	}
	
	container := args[0]
	command := args[1]
	
	// 这里实现实际的命令执行逻辑
	fmt.Printf("Executing command '%s' in container %s\n", command, container)
	return nil
}

对应的Bash脚本示例:

#!/bin/bash

# 检查Go函数是否存在
if goprog apicheck "listContainers" | grep -q "true"; then
    # 使用Go版本
    goprog apicall "listContainers" "$@"
else
    # 回退到Bash版本
    bash_list_containers "$@"
fi

# 批量迁移检查
MIGRATE_FUNCTIONS=("listContainers" "createContainer" "removeContainer" "executeCommand")

for func in "${MIGRATE_FUNCTIONS[@]}"; do
    if goprog apicheck "$func" | grep -q "true"; then
        # 创建包装函数
        eval "
        ${func}() {
            goprog apicall \"${func}\" \"\$@\"
        }
        "
        echo "Migrated $func to Go version"
    fi
done

# 动态获取所有可用函数
AVAILABLE_FUNCTIONS=$(goprog apilist | tr -d '[]"' | tr ',' '\n')

echo "Available Go API functions:"
echo "$AVAILABLE_FUNCTIONS"

构建和测试命令:

# 构建Go程序
go build -o goprog main.go

# 测试API列表
./goprog apilist
# 输出: ["listContainers","createContainer","removeContainer","executeCommand"]

# 测试函数检查
./goprog apicheck listContainers
# 输出: true

# 测试帮助信息
./goprog apihelp createContainer
# 输出: createContainer <name> <image> - 创建新容器

# 测试API调用
./goprog apicall createContainer mycontainer fedora:latest
# 输出: Creating container mycontainer from image fedora:latest

这个方案允许你在Bash脚本中逐步替换函数调用,同时保持向后兼容性。每个Bash函数可以先检查Go版本是否存在,如果存在则调用Go版本,否则继续使用Bash实现。

回到顶部