go 命令备忘

这里简单记录几个常用的go命令。

go build

Go语言的编译速度非常快。Go 1.9 版本后默认利用Go语言的并发特性进行函数粒度的并发编译。

Go语言的程序编写基本以源码方式,无论是自己的代码还是第三方代码。

Go语言以 GOPATH 作为工作目录和一套完整的工程目录规则。因此 Go 语言中编译时无须像 C++ 一样配置各种包含路径、链接库地址等。

go build 命令主要用于编译代码,若有必要,会同时编译与之相关联的包。

go build 有很多种编译方法,如无参数编译、文件列表编译、指定包编译等,使用这些方法都可以输出可执行文件。

注意:需要编译的项目/文件一定要有 main package,且包含main函数,否则无法 build。

  1. go build 命令默认会编译当前目录下的所有 go 文件。如果没有main package,不会产生任何文件。

  2. 如果你只想编译其中某一个文件,可以在 go build 之后加上文件名,例如 go build a.go。

  3. 如果需要在 $GOPATH/bin 目录下生成相应的可执行文件,需要执行 go install 或者使用 go build -o </path/xxx>。

  4. go build 会忽略目录下以”_”或者”.”开头的go文件。

  5. 如果你的源代码针对不同的操作系统需要不同的处理,那么你可以根据不同的操作系统后缀来命名文件。例如有一个读取数组的程序,它对于不同的操作系统可能有如下几个源文件:

    array_linux.go 
    array_darwin.go 
    array_windows.go 
    array_freebsd.go
    

    go build 的时候会选择性地编译以系统名结尾的文件(Linux、Darwin、Windows、Freebsd)。例如Linux系统下面编译只会选择array_linux.go文件,其它系统命名后缀文件全部忽略。

go clean

用来移除当前源码包里面编译生成的文件

go get

用来动态获取远程代码包的,目前支持的有BitBucket、GitHub、Google Code和Launchpad。

这个命令在内部实际上分成了两步操作:

  1. 下载源码包
  2. 执行go install。

下载源码包的go工具会自动根据不同的域名调用不同的源码工具。

go install

go install 命令在内部实际上分成了两步操作:

  1. 生成结果文件(可执行文件或者.a包)
  2. 把结果移到 $GOPATH/pkg 或者 $GOPATH/bin

go test

自动读取源码目录下面名为 *_test.go 的文件,生成并运行测试用的可执行文件。

go doc

go doc 可以打印当前目录下文件的方法和类型定义

go doc
go doc -u xxxStruct
go doc http.Request

godoc

go get -v  golang.org/x/tools/cmd/godoc
godoc -http=:6060

以网页方式展现的Go文档,使得我们在不方便访问Go语言官方站点的情况下也可以查看Go语言文档,并且可以看自己代码的文档。

参考资料


搭建个人在线IDE —— vscode online

前年,微软在 Ignite 2019 大会上,正式发布了 「Visual Studio Online」。其中包含了微软托管的 WebVSCode,后来发布的 VSCode 1.40 支持开发者直接从 VSCode 的源代码编译出 WebVSCode

由于我目前开发环境众多,win/mac/Linux都有,虽然已经对代码做了容器化,还是需要重复下载代码,配置IDE环境,挺麻烦的。这篇文章简单记录我搭建在线IDE的过程。

安装

配置VSCode Online有几种方法:

  • 微软官方提供一个收费版本(含azure的服务器费用,捆绑销售),不推荐
  • 下载 VSCode 源代码,编译以后通过yarn web启动。配置难度大,不推荐
  • 通过 Code-Server 安装: https://code-server.dev/
  • 使用/修改现成的 docker 镜像:linuxserver/docker-code-server

我目前的方法是自行编译 linuxserver 提供的镜像。实际上只用linuxserver 的镜像已经足够了,没有特殊自定义要求的可以跳过这一部分。

这个文件是 code-server实际的启动命令,可以按需自定义修改:

#!/usr/bin/with-contenv bash

if [ -n "${PASSWORD}" ] || [ -n "${HASHED_PASSWORD}" ]; then
  AUTH="password"
else
  AUTH="none"
  echo "starting with no password"
fi

if [ -z ${PROXY_DOMAIN+x} ]; then
  PROXY_DOMAIN_ARG=""
else
  PROXY_DOMAIN_ARG="--proxy-domain=${PROXY_DOMAIN}"
fi

exec \
	s6-setuidgid abc \
		/usr/local/bin/code-server \
			--bind-addr 0.0.0.0:8443 \
			--user-data-dir /config/data \
			--extensions-dir /config/extensions \
			--disable-telemetry \
			--auth "${AUTH}" \
			"${PROXY_DOMAIN_ARG}" \
			/config/workspace

例如我将端口由 8443 改成了 80。

--bind-addr 0.0.0.0:80

修改 dockerfile 中的 expose 内容

EXPOSE 8443

改为

EXPOSE 80

在项目主目录进行编译:

docker build \
  --no-cache \
  --pull \
  -t kelvinblood/code-server:base .

运行

参考 https://github.com/linuxserver/docker-code-server 的帮助内容,我做了调整:

docker-compose.yml:

version: "2.1"
services:
  code-server:
    image: kelvinblood/code-server:base
    container_name: code-server
    network_mode: bridge
    environment:
      - PUID=0
      - PGID=0
      - TZ=Asia/Shanghai
      - PASSWORD=xxxx  #optional
#      - HASHED_PASSWORD= #optional
#      - SUDO_PASSWORD=abcd #optional
#      - SUDO_PASSWORD_HASH= #optional
      - PROXY_DOMAIN=xxx.kelu.org #optional
    volumes:
      - ./config:/config
      - /root/abc:/Workspace/abc
      - /root/xxx:/Workspace/xxx
#    ports:
#      - xxxx:80
    restart: unless-stopped

配置nginx

image-20210407170010631

在这一块我做多了几个安全认证,这几项都不是必须的,可以跳过,一个是使用https访问:

  ssl_certificate /etc/letsencrypt/live/code.kelu.org/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/code.kelu.org/privkey.pem;
  ssl_session_timeout 5m;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers AESGCM:ALL:!DH:!EXPORT:!RC4:+HIGH:!MEDIUM:!LOW:!aNULL:!eNULL;
  ssl_prefer_server_ciphers on;

一个是nginx密码

  auth_basic "Please input password";
  auth_basic_user_file /etc/nginx/passwd/goaccess;

一个是允许特定IP访问:

     satisfy any;
     allow xxx;
     allow xxx;
     deny all;

最后要注意配置 websocket 代理配置,最后两行:

  location / {
     satisfy any;
     allow 121.31.34.22;
     allow 61.51.94.34;
     deny all;

     proxy_pass http://code-server.bj1.local;
     proxy_read_timeout 300s;
     proxy_send_timeout 300s;

     proxy_set_header Host $host;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     
     proxy_set_header Accept-Encoding gzip;

     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";
  }
}

界面预览

image-20210407170700847

装上自己需要的插件,就可以开始开发了。

image-20210407171100100

参考资料


go 字符串拆分 split

简单记录 golang 使用 regexp.MustCompile 进行字符串拆分的用法。

假设目前的输入值为:

        "spec":{  
            "Container": {
                "default.cpu": "200m",
                "default.memory": "200Mi",
                "defaultRequest.cpu": "100m",
                "defaultRequest.memory": "100Mi",
                "max.cpu": "2",
                "max.memory": "1Gi",
                "maxLimitRequestRatio.cpu": "5",
                "maxLimitRequestRatio.memory": "4",
                "min.cpu": "100m",
                "min.memory": "3Mi"
            },
            "Pod": {
                "max.cpu": "2",
                "max.memory": "1Gi",
                "maxLimitRequestRatio.cpu": "5",
                "maxLimitRequestRatio.memory": "4",
                "min.cpu": "100m",
                "min.memory": "3Mi"
            }
        }

我们将 . 号前后的字符进行拆分,整合 key,append 最后输出为类似如下格式的 interface:

image-20210323120833531

嵌套两个 for 循环即可完成。

func exchange(spec interface{}) interface{} {
    var result []interface{}

	for kind, u := range spec.(map[string]interface{}) {
		limitsRule := make(map[string]interface{})
		limitsRule["type"] = kind
		for k, _ := range u.(map[string]interface{}) {
			s := regexp.MustCompile("[.!?]").Split(k, 2)
			limitsRule[s[0]] = nil
		}

		for k, v := range u.(map[string]interface{}) {
			s := regexp.MustCompile("[.!?]").Split(k, 2)
			var tmp map[string]string
			if limitsRule[s[0]] == nil {
				tmp = make(map[string]string)
			} else {
				tmp = limitsRule[s[0]].(map[string]string)
			}
			tmp[s[1]] = v.(string)
			limitsRule[s[0]] = tmp
		}

		result = append(result, limitsRule)
	}

	return result
}

参考资料


go 使用 ast 优化结构体打印样式

与 kubernetes 代码交互,常有难以名状的interface,需要猜测里面的数据,非常痛苦。使用 ast 打印可以一目了然里面的数据,是开发过程中不得或缺的辅助工具。这篇文章简单记录我的使用情况。


package xxx

import (
	"go/ast"
	"go/token"	
)

func GuessType(obj interface{}) {
	fset := token.NewFileSet()
	ast.Print(fset, obj)
}

将 obj 传入 GuessType 方法即可:

GuessType(obj)

参考资料


go 基本类型的互转

转转转,类型转换是静态语言(强类型语言)在代码时候经常要进行的动作。这篇文章是我 golang 开发中类型转换的一些笔记。interface、struct、map相关的转换参考前文《golang 解析 map[string]interface{} 和 json 到 struct》

package xxx

import (
    "strconv"
)

int -> string

// int64
var Int64 int64 = 9223372036854775807
str := strconv.FormatInt(Int64, 10)

// unit64
var Uint64 uint64 = 18446744073709551615
str2 := strconv.FormatUint(Uint64, 10)

// int32
var test int32 = xxx
str3 := strconv.Itoa(int(test)) 

// int32 
// 对比: <https://stackoverflow.com/questions/39442167/convert-int32-to-string-in-golang>

s = String(i)                       takes:  5.5923198s
s = String2(i)                      takes:  5.5923199s
s = strconv.FormatInt(int64(i), 10) takes:  5.9133382s
s = strconv.Itoa(int(i))            takes:  5.9763418s
s = fmt.Sprint(i)                   takes: 13.5697761s

string -> int

// int64
var strInt64 string = "9223372036854775807"
convertedStrInt64, _ := strconv.ParseInt(strInt64, 10, 64)

// uint64
var strUint64 string = "18446744073709551615"
convertedStrUint64, _ := strconv.ParseUint(strUint64, 10, 64)

interface -> map[string] interface

// data 为 interface
data.(map[string]interface{})

interface -> string

// data 为 interface
data.(string)

string->[]byte

var str string = "test"
data := []byte(str)

[]byte->string

var data [10]byte 
string := string(data)

参考资料


将 Kubernetes unstructured 类型转为对象

使用代码和k8s交互,主要用到 client-go 这个库中的两个主要 APIs: kubernetes.Interface API 和非结构化的 dynamic.Interface API。

在使用 dynamic.Interface 时,我们经常需要在 string,unstructured ,对象, map[string] interface{} 转来转去,本文介绍将非结构化的类型 (unstructured) 与对象互转的两个方法。

方法一:json

使用 json 包将 unstructured 序列化为 []byte ,再反序列化为对象,这是 golang 里最通用的做法了。不过这个做法有点局限性,如果内容存在一些特殊字符容易在 json.Unmarshal 的时候报错,这时候还是使用方法二,官方的比较靠谱。

unstructured -> []byte -> object

import "encoding/json"
...

    // 从k8s返回值里构造一个 unstructured 
    var utd *unstructured.Unstructured
	utd, err = dynamicClient.Resource(gvr).Create(xxxUnstructured, metav1.CreateOptions{})
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
    // unstructured -> []byte
	result, err := utd.MarshalJSON()
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

    // []byte -> obj
	var obj XxxObject
	if err := json.Unmarshal(result, &obj); err != nil {
		logrus.Error(err)
		return nil, err
	}

object -> []byte -> unstructured

ps: 对象转为[]byte时需要注意的一点是,object里不能存在 map[interface{}] interface{} 类型,如果存在此种类型,一定要在源头将其转换为 map[string]interface 类型,否则无法使用json包。

json: unsupported type: map[interface {}]interface {}

相关 issue参考:map[interface{}]interface{} on a map[string]interface{}

import "gopkg.in/yaml.v3"
...

    //// 从k8s返回值里构造一个 object 
	var obj XxxObj
	err := yaml.Unmarshal([]byte(yamlData), &obj)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

    // obj ->  []byte
	data, err := json.Marshal(&obj)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
	// []byte -> Unstructured
	utd := &unstructured.Unstructured{}
	err := json.Unmarshal(data, &utd.Object)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

方法二:runtime.UnstructuredConverter

runtime.DefaultUnstructuredConverter 基本上满足了我们大部分场景。

unstructured -> obj


import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/cluster-api/api/v1alpha2"
)
...

    // 从k8s返回值里构造一个 unstructured 
    var utd *unstructured.Unstructured
	utd, err = dynamicClient.Resource(gvr).Create(xxxUnstructured, metav1.CreateOptions{})
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
    
    // unstructured -> obj
	var obj XxxObject
	runtime.DefaultUnstructuredConverter.FromUnstructured(utd.UnstructuredContent(), &cpol)

obj -> unstructured

utd, err := runtime.UnstructuredConverter.ToUnstructured(obj)

objOther -> objK8sVx

假定c是一个第三方封装的object,内容包含了v1node的数据。

		node := c.(interface{})
		utd, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&node)
		if err != nil {
			logrus.Error(err)
		}

		v1node := &v1.Node{}
		err = runtime.DefaultUnstructuredConverter.FromUnstructured(utd, v1node)
		if err != nil {
			logrus.Error(err)
		}

参考资料