搭建个人在线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)
		}

参考资料


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)

参考资料