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

参考资料


go xorm 入门笔记

xorm 是一个简单而强大的Go语言 ORM 库,目前支持了众多的数据库,包括:

最近使用它进行数据库操作,这篇文章简单记录使用情况。

官方网址:https://xorm.io/,官方手册:https://gobook.io/read/gitea.com/xorm/manual-zh-CN/

安装

go get xorm.io/xorm

使用相应的数据库,需要安装相应的驱动,如果使用的是MySQL,需要如下安装:

go get -u github.com/go-sql-driver/mysql

基本使用

package main

import (
    "fmt"
    "github.com/go-xorm/xorm"
    _ "github.com/go-sql-driver/mysql"  // 默认会执行init初始化一些操作的
    "xorm.io/core"
)

func main(){
    //1.创建数据库引擎对象
    engine,err := xorm.NewEngine("mysql","root:root@(127.0.0.1:3306)/elmcms?charset=utf8");
    if err != nil {
        panic(err.Error())
    }
    
    //2.数据库引擎关闭
    defer engine.Close()
    
    //3.数据库引擎设置
    engine.ShowSQL(true)  //设置显示sql语句  会在控制台当中展示
    engine.Logger().SetLevel(core.LOG_DEBUG)   //设置日志级别
    engine.SetMaxOpenConns(10)    //设置最大链接数量
    
    loc, _ := time.LoadLocation("Asia/Shanghai") //设置时区
  	Engine.DatabaseTZ = loc // 设置时区
  	Engine.TZLocation = loc // 设置时区

    //3.简单的一些使用
    //判断表结构是否存在
    exist, err := engine.IsTableExist(XxxObj{})
    if err != nil {
        panic(err.Error())
    }
    
    if ! exist {
        engine.CreateTables(XxxObj{})
    }
}

结构体映射

通过结构体映射自动创建数据库表以及判断表是否为空或者是否存在.

参考:https://gobook.io/read/gitea.com/xorm/manual-zh-CN/chapter-02/1.mapping.html

type XxxObj struct {
    Id         int64     `xorm:"pk autoincr"`   //主键自增
    PersonName string    `xorm:"varchar(24)"`   //可变字符
    PersonAge  int       `xorm:"int default 0"` //默认值
    PersonSex  int       `xorm:"notnull"`       //不能为空
    City       CityTable `xorm:"-"`             //不映射该字段 那就不会在数据库里面创建该字段
}

简单用法

查询

    var obj XxxObj
    var objList []XxxObj
    
    // id查询
    engine.Id(1).Get(&obj)

	// where多条件查询
 	engine.Where(" a = ? and b = ?", 30, 1).Get(&obj)
 	
 	// and or 查询
 	engine.Where(" a = ?", 30).And("b = ?", 1).Find(&objList)
 	engine.Where(" a = ?", 30).Or("b = ?", 1).Find(&objList)
 	
 	// 原生sql语句查询 支持like
 	engine.SQL(" select * from xxx_table where a like '%i%' ").Find(&objList)
 	
 	// orderby
 	engine.OrderBy(" a desc ").Find(&objList)
 	
 	// 特定字段
 	engine.Cols("a", "b").Find(&obj)

更新

 	//更新操作
    xxxUpdate := XxxObj{
        a: "name",
        b:  30,
        c:  1,
    }
    rowNum2, err := engine.Id(1).Cols("xxx","yyy").Update(&personUpdate) //bool类型的更新,有时候不能写入,所以要加上 Cols 强制指定更新的字段。
    fmt.Println(rowNum2) // rowNum2也是表示受影响的行数

    //统计功能count
    count, err := engine.Count(new(XxxObj))
    fmt.Println("persontable表总记录数:",count)

事务操作

    //事务操作
    xxxArray := []XxxObj{
        XxxObj{
            a: "Jack",
            b:  28,
            c:  1,
        },
        XxxObj{
            a: "Mali",
            b:  28,
            c:  1,
        },
        XxxObj{
            a: "Ruby",
            b:  28,
            c:  1,
        },
    }

    session := engine.NewSession()
    session.Begin()
    for i:=0;i<len(xxxArray);i++{
        _, err = session.Insert(xxxArray[i])
        if err != nil {
            session.Rollback()
            session.Close()
        }
    }
    err = session.Commit()
    session.Close()
    if err != nil {
        panic(err.Error())
    }

分页

count, err := models.Engine.Cols("id", "name", "create_time").OrderBy("create_time").Limit(pageSize, skip).FindAndCount(&lists)

参考资料


使用 client-go 操作 kubernetes 的 crd 资源

最近在开发 k8s crd 相关的操作,这里简单的记录一下通过 client-go 增删改查 k8s crd 资源的方法。

示例CRD

这里我以第三方的crd: kyverno 作为例子。

kyverno 的 crd 结构体定义在这里: pkg/api/kyverno/v1,主要是一下两个文件(为了引入尽可能少的文件,编译报错时可以把某些类型改成interface)

重点的 cluster policy 和 List,以及内部的 Rule 的结构体如下:

type ClusterPolicy struct {
	metav1.TypeMeta   `json:",inline,omitempty" yaml:",inline,omitempty"`
	metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
	Spec Spec `json:"spec" yaml:"spec"`
	Status PolicyStatus `json:"status,omitempty" yaml:"status,omitempty"`
}

type ClusterPolicyList struct {
	metav1.TypeMeta `json:",inline" yaml:",inline"`
	metav1.ListMeta `json:"metadata" yaml:"metadata"`
	Items           []ClusterPolicy `json:"items" yaml:"items"`
}

type Rule struct {
	Name string `json:"name,omitempty" yaml:"name,omitempty"`
	Context []ContextEntry `json:"context,omitempty" yaml:"context,omitempty"`
	MatchResources MatchResources `json:"match,omitempty" yaml:"match,omitempty"`
	ExcludeResources ExcludeResources `json:"exclude,omitempty" yaml:"exclude,omitempty"`
	AnyAllConditions apiextensions.JSON `json:"preconditions,omitempty" yaml:"preconditions,omitempty"`
	Mutation Mutation `json:"mutate,omitempty" yaml:"mutate,omitempty"`
	Validation Validation `json:"validate,omitempty" yaml:"validate,omitempty"`
	Generation Generation `json:"generate,omitempty" yaml:"generate,omitempty"`
}

部署 kyverno 的 crd:

kubectl create -f https://raw.githubusercontent.com/kyverno/kyverno/main/definitions/release/install.yaml

查看 k8s 上的api资源有哪些:

kubectl api-resources

image-20210323090256151

List

相当于 kubectl get xxx 命令,我们通过 k8s.io/client-go/dynamic 里的 Interface 提供的方法来操作 crd 资源。

package main

import (
        "encoding/json"
        "fmt"
        "os"
        "path/filepath"

        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/client-go/dynamic"
        "k8s.io/client-go/tools/clientcmd"
)

// getGVR :- gets GroupVersionResource for dynamic client
func getGVR(group, version, resource string) schema.GroupVersionResource {
	return schema.GroupVersionResource{Group: group, Version: version, Resource: resource}
}

func listKyverno(client dynamic.Interface, ns string) (*ClusterPolicyList, error) {
	    gvr := getGVR("kyverno.io", "v1", "clusterpolicies")

        if (ns == ''){
        	list, err := dynamicClient.Resource(gvr).List(metav1.ListOptions{})
        } else {
        	list, err := dynamicClient.Resource(gvr).Namespace(namespace).List(metav1.ListOptions{})
        }
        
	
        if err != nil {
                return nil, err
        }
        
        data, err := list.MarshalJSON()
        if err != nil {
                return nil, err
        }
        
        var list ClusterPolicyList
        if err := json.Unmarshal(data, &list); err != nil {
                return nil, err
        }
        
        return &list, nil
}

func main() {
        kubeconfig := filepath.Join(os.Getenv("HOME"), ".kube", "config")
        config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
        if err != nil {
                panic(err)
        }

        client, err := dynamic.NewForConfig(config)
        if err != nil {
                panic(err)
        }
        
        list, err := listKyverno(client, "")
        if err != nil {
                panic(err)
        }
        
        for _, t := range list.Items {
                fmt.Printf("%s %s %s %s\n", t.Namespace, t.Name, t.Spec.CronSpec, t.Spec.Image)
        }
}

Get

func getKyverno (client dynamic.Interface, namespace string, name string) (*ClusterPolicy, error) {
        gvr := getGVR("kyverno.io", "v1", "clusterpolicies")
        
        // Unstructured
        utd, err := client.Resource(gvr).Namespace(namespace).Get(name, metav1.GetOptions{})
        
        // Unstructured -> cpol
        var cpol ClusterPolicy
        runtime.DefaultUnstructuredConverter.FromUnstructured(utd.UnstructuredContent(), &cpol)
        logrus.Info("kube kyverno cpol ", cpol.Name, ", version:", cpol.Generation)
        
        return &cpol, nil
}

Create

前端 Post 传输 yaml 信息,传输过来实际上是字符串。

image-20210323101755887

import "gopkg.in/yaml.v3"

func createKyvernoFrom(client dynamic.Interface, namespace string, yamlData string) (*ClusterPolicy, error) {
    gvr := getGVR("kyverno.io", "v1", "clusterpolicies")
    
    // yaml string -> cpol
	var cpol ClusterPolicy
	err := yaml.Unmarshal([]byte(yamlData), &cpol)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

    // cpol ->  []byte
	data, err := json.Marshal(&cpol)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
	// []byte -> Unstructured
	obj := &unstructured.Unstructured{}
	err := json.Unmarshal(data, &obj.Object)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
	// cpol -> unstructed 也可以直接使用 runtime.DefaultUnstructuredConverter.FromUnstructured 类似的方法做一个反转换,这部分可以从参考我另一篇blog: https://blog.kelu.org/tech/2021/03/14/go-unstructured.html
	
	// Unstructured create k8s 
    var utd *unstructured.Unstructured
	utd, err = dynamicClient.Resource(gvr).Create(obj, 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 -> cpol
	var resultCpol ClusterPolicy
	if err := json.Unmarshal(result, &resultCpol); err != nil {
		logrus.Error(err)
		return nil, err
	}
    
    return &resultCpol, nil
}

Update

utd, err = client.Resource(gvr).Namespace(namespace).Update(obj, metav1.UpdateOptions{})

Patch

 _, err := client.Resource(gvr).Namespace(namespace).Patch(name, pt, data, metav1.PatchOptions{})

Delete

client.Resource(gvr).Namespace(namespace).Delete(name, nil)

参考资料


go 主动退出进程

有时候简单调试应用,懒得写 go test,可以简单的使用 stdlib os 包,在main函数中直接退出应用程序。

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println("Hello World")

	// 正常退出
	os.Exit(0)
}