go版本,相比bash版本加强了并发能力
/*
* hack-go-get-kubernetes-version-xx.xx.xx.go
* example:
* go run $s4y/hack-go-get-kubernetes-version-xx.xx.xx.go v1.22.6
* 1. 依赖的kubernetes版本号
*/
// Package main is the entry of script.
package main
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"sync"
"syscall"
)
func main() {
fmt.Println("hack-go-get-kubernetes-version-xx.xx.xx begin")
defer fmt.Println("hack-go-get-kubernetes-version-xx.xx.xx end")
var err error
var strExample = "go run $s4y/hack-go-get-kubernetes-version-xx.xx.xx.go v1.22.6"
defer func() { fmt.Printf("err: %v, example: %s\n", err, strExample) }()
var errParamWrong = errors.New("param wrong")
// 获取当前目录
var pwd string
if pwd, err = os.Getwd(); err != nil {
return
}
fmt.Printf("pwd: %s\n", pwd)
// 获取命令行参数
var lenOsArgs = len(os.Args)
fmt.Printf("command-args-cnt: %d, command-args: %v\n", lenOsArgs, os.Args)
if lenOsArgs != 2 {
err = errors.New("lenOsArgs != 2")
return
}
// 获取依赖的kubernetes版本号
var version = os.Args[1]
var idxV = strings.IndexByte(version, 'v')
if idxV == -1 {
err = errParamWrong
return
}
var versionTrimV = version[idxV+1:]
// 解析k8s的go.mod文件
var strCmd = fmt.Sprintf(`
curl -x $http_proxy -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v%s/go.mod | grep "staging" | sed -n 's|.*k8s.io/\(.*\) => ./staging/src/k8s.io/.*|k8s.io/\1|p'
`, versionTrimV)
var cmd = NewCommand(strCmd)
if err = cmd.Exec(); err != nil {
return
}
var stdOut = cmd.GetStdOut()
if stdOut == "" {
err = fmt.Errorf("%s stdOut is null", strCmd)
return
}
var stageRepos = strings.Split(stdOut, "\n")
var lenStageRepos = len(stageRepos)
var stageVersions = make([]string, lenStageRepos)
// 并发下载k8s依赖的包缓存到本地
var wg sync.WaitGroup
wg.Add(lenStageRepos)
for idx, repo := range stageRepos {
go func(idx int, repo string) {
defer wg.Done()
if repo == "" {
return
}
var strCmd = fmt.Sprintf(`
go mod download -json "%s@kubernetes-%s" | grep "Version" | sed -n 's|.*"Version": "\(.*\)".*|\1|p'
`, repo, versionTrimV)
var cmd = NewCommand(strCmd)
if err := cmd.Exec(); err != nil {
fmt.Printf("strCmd: %s, err: %s\n", strCmd, err.Error())
return
}
var stdOut = cmd.GetStdOut()
if stdOut == "" {
fmt.Printf("strCmd: %s, stdOut is null\n", strCmd)
return
}
stageVersions[idx] = fmt.Sprintf("\t%s => %s %s\n",
repo, repo, stdOut,
)
}(idx, repo)
}
wg.Wait()
// 以追加方式写入到go.mod文件末尾, 并获取k8s包
var strToWrite strings.Builder
strToWrite.Grow(1024)
strToWrite.WriteString("\nreplace(\n")
for _, str := range stageVersions {
strToWrite.WriteString(str)
}
strToWrite.WriteString(")\n")
fmt.Printf("write to %s/go.mod\n", pwd)
strCmd = fmt.Sprintf(`
echo "%s" >> ./go.mod && go get "k8s.io/kubernetes@v%s"
`, strToWrite.String(), versionTrimV)
cmd = NewCommand(strCmd)
err = cmd.Exec()
fmt.Println(cmd.StringWithExecErr(err))
return
}
//Command defines commands to be executed and captures std out and std error
type Command struct {
Cmd *exec.Cmd
StdOut []byte
StdErr []byte
ExitCode int
}
func NewCommand(command string) *Command {
return &Command{Cmd: exec.Command("bash", "-c", command)}
}
// Exec run command and exit formatted error, callers can print err directly
// Any running error or non-zero exitcode is consider as error
func (cmd *Command) Exec() error {
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Cmd.Stdout = &stdoutBuf
cmd.Cmd.Stderr = &stderrBuf
errString := fmt.Sprintf("failed to exec '%s'", cmd.GetCommand())
err := cmd.Cmd.Start()
if err != nil {
errString = fmt.Sprintf("%s, err: %v", errString, err)
return errors.New(errString)
}
err = cmd.Cmd.Wait()
if err != nil {
cmd.StdErr = stderrBuf.Bytes()
if exit, ok := err.(*exec.ExitError); ok {
cmd.ExitCode = exit.Sys().(syscall.WaitStatus).ExitStatus()
errString = fmt.Sprintf("%s, err: %s", errString, stderrBuf.Bytes())
} else {
cmd.ExitCode = 1
}
errString = fmt.Sprintf("%s, err: %v", errString, err)
return errors.New(errString)
}
cmd.StdOut, cmd.StdErr = stdoutBuf.Bytes(), stderrBuf.Bytes()
return nil
}
func (cmd Command) GetCommand() string {
return strings.Join(cmd.Cmd.Args, " ")
}
func (cmd Command) GetStdOut() string {
if len(cmd.StdOut) != 0 {
return strings.TrimSuffix(string(cmd.StdOut), "\n")
}
return ""
}
func (cmd Command) GetStdErr() string {
if len(cmd.StdErr) != 0 {
return strings.TrimSuffix(string(cmd.StdErr), "\n")
}
return ""
}
func (cmd Command) StringWithExecErr(errExec error) (res string) {
var resBuild strings.Builder
resBuild.Grow(1024)
// 写入执行的命令
resBuild.WriteString("strCmd: ")
for _, args := range cmd.Cmd.Args {
resBuild.WriteByte(' ')
resBuild.WriteString(args)
}
resBuild.WriteByte('\n')
// 写入执行的err
if errExec == nil {
resBuild.WriteString("err: nil\n")
} else {
resBuild.WriteString("err: ")
resBuild.WriteString(errExec.Error())
resBuild.WriteByte('\n')
}
// 写入stdOut
if len(cmd.StdOut) == 0 {
resBuild.WriteString("stdOut is null\n")
} else {
resBuild.WriteString("stdOut: ")
resBuild.Write(cmd.StdOut)
}
// 写入stdErr
if len(cmd.StdErr) == 0 {
resBuild.WriteString("stdErr is null\n")
} else {
resBuild.WriteString("stdErr: ")
resBuild.Write(cmd.StdErr)
}
// 写入exitCode
resBuild.WriteString("exitCode: ")
resBuild.WriteString(strconv.Itoa(cmd.ExitCode))
res = resBuild.String()
return
}
bash版本
#########################################################################
# File Name: hack-go-get-kubernetes-version-xx.xx.xx.sh
# Author: xiaoyang.chen
# mail: xiaoyang.chen@???.com
# Created Time: 五 3/25 17:01:05 2022
#########################################################################
#!/bin/bash
# using example: hack-go-get-kubernetes-version-xx.xx.xx.sh v1.22.6
# from https://github.com/kubernetes/kubernetes/issues/79384
# abursavich commented on 15 Aug 2019
# '-e': 当一个命令失败时, 立即退出
# '-u': 遇到未定义的变量, 报错并立即退出
# '-o pipefail': 在运行脚本时, 通常我们只看最后一个命令的退出码, 例如'invalid_command || echo "invalid command"', 我们获得退出码为0, 因为我们获得整个管道的最后一个命令, 加上'-o pipefail', 则会根据整个管道链的退出码来判断, 只有所有退出码为0, 退出码才会为0
set -euo pipefail
VERSION=${1#"v"}
# -z True if the length of $VERSION is zero.
if [ -z "$VERSION" ]; then
echo "Must specify version!"
exit 1
fi
# curl -sS -s silent, -S with -s, will print error message when curl fail
# in k8s go.mod:
# k8s.io/api => ./staging/src/k8s.io/api
# k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver
# k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery
# k8s.io/apiserver => ./staging/src/k8s.io/apiserver
# s为substitution替换, |为分界符, p指仅将替换后的行输出到标准输出, 其他不输出
# \1是()中匹配到的内容的第一个匹配项, ()会将匹配到的内容保存进缓冲区, 缓冲区编号从1开始, 最多可存储99个捕获的子表达式, 每个缓冲区都可以使用\n访问, 其中n为一个标识特定缓冲区的一位或两位十进制数
# 解析k8s的go.mod文件
MODS=($(
curl -x $http_proxy -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${VERSION}/go.mod | grep "staging" | sed -n 's|.*k8s.io/\(.*\) => ./staging/src/k8s.io/.*|k8s.io/\1|p'
))
# 并发下载k8s依赖的包缓存到本地
declare -a versions
for MOD in "${MODS[@]}"; do
versions+=("$MOD => $MOD `go mod download -json "${MOD}@kubernetes-${VERSION}" | grep "Version" | sed -n 's|.*"Version": "\(.*\)".*|\1|p' &`");
done
wait
# 以追加方式写入到go.mod文件末尾
stringToWrite="\nreplace(\n"
for v in "${versions[@]}"; do
stringToWrite+="\t$v\n";
done
stringToWrite+=")\n"
echo "write to `pwd`/go.mod";
echo "$stringToWrite" >> ./go.mod;
go get "k8s.io/kubernetes@v${VERSION}"