k8s 将k8s作为module使用,进行二开,解决k8s replace的staging本地依赖问题

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}"

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值