文章目录
1. 包的依赖
2. main.go
2.1 先分析cli.v1的应用
2.2 LoggerWithConfig是这么含义?
2.2.1 `fasttemplate.New(config.Format, "${", "}")`是什么?
2.2.2 `config.colorer = color.New()`代码中`new()`又如何理解呢?
2.2.3 sync.Pool又是什么?
2.3 白名单、证书、密钥的判断
3. 总结相关阅读:
Kubernetes ImagePolicyWebhook与ValidatingAdmissionWebhook【1】动手实践感受区别所在
KubernetesImagePolicyWebhook与ValidatingAdmissionWebhook【2】Image_Policy.go源码解析
Kubernetes ImagePolicyWebhook与ValidatingAdmissionWebhook【3】validating_admission.go源码解析
Kubernetes ImagePolicyWebhook与ValidatingAdmissionWebhook【4】main.go源码解析
kubernetes 快速学习手册
1. 包的依赖
go fmt包详解
go os包文件操作详解
go strings包详解
echo包详解
echo源码
项目源码:https://github.com/kainlite/kube-image-bouncer
2. main.go
首先我们了解一下依赖包的各个用途。
import ( "fmt" //打印输出 "os" //系统管理 "strings" //字符串管理 "github.com/labstack/echo" //可扩展,轻量级的web框架 "github.com/labstack/echo/middleware" //echo web框架中间件 "github.com/labstack/gommon/log" //自定义的日志输出 "gopkg.in/urfave/cli.v1" //用于在 Go 中构建命令行应用程序 "github.com/kainlite/kube-image-bouncer/handlers" )
下面的代码其实是以cli包与echo包为主的运行,如果具体学习cli的用法可以参考https://pkg.go.dev/gopkg.in/urfave/cli.v1
func main() { //定义参数 var cert, key, whitelist string //证书,密钥,白名单 var port int //webhook的端口 var debug bool //debug模式 //定义命令对象 app := cli.NewApp() app.Name = "kube-image-bouncer" app.Usage = "webhook endpoint for kube image policy admission controller" //定义命令参数 app.Flags = []cli.Flag{ cli.StringFlag{ Name: "cert, c", Usage: "Path to the certificate to use", EnvVar: "BOUNCER_CERTIFICATE", Destination: &cert, }, cli.StringFlag{ Name: "key, k", Usage: "Path to the key to use", EnvVar: "BOUNCER_KEY", Destination: &key, }, cli.StringFlag{ Name: "registry-whitelist", Usage: "Comma separated list of accepted registries", EnvVar: "BOUNCER_REGISTRY_WHITELIST", Destination: &whitelist, }, cli.IntFlag{ Name: "port, p", Value: 1323, Usage: "Port to listen to", EnvVar: "BOUNCER_PORT", Destination: &port, }, cli.BoolFlag{ Name: "debug", Usage: "Enable extra debugging", EnvVar: "BOUNCER_DEBUG", Destination: &debug, }, } //定义命令的调用方式 app.Action = func(c *cli.Context) error { e := echo.New() e.POST("/image_policy", handlers.PostImagePolicy()) e.POST("/", handlers.PostValidatingAdmission()) e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "method=${method}, uri=${uri}, status=${status}\n", })) if debug { e.Logger.SetLevel(log.DEBUG) } if whitelist != "" { handlers.RegistryWhitelist = strings.Split(whitelist, ",") fmt.Printf( "Accepting only images from these registries: %+v\n", handlers.RegistryWhitelist) fmt.Println("WARN: this feature is implemented only by the ValidatingAdmissionWebhook code") } else { fmt.Println("WARN: accepting images from ALL registries") } var err error if cert != "" && key != "" { err = e.StartTLS(fmt.Sprintf(":%d", port), cert, key) } else { err = e.Start(fmt.Sprintf(":%d", port)) } if err != nil { return cli.NewExitError(err, 1) } return nil } //运行app对象 app.Run(os.Args) }
main.go主要设计两个依赖包的用法:
- cli.v1
- echo
2.1 先分析cli.v1的应用
创建一个newapp,go build 生成二进制命令则是app.name的值。
这里是除了配置命令的名字,用法说明,还有配置命令行参数,这里有包含四个,分别是证书,密钥、白名单、端口。,白名单是我们需要的仓库,即来自白名单的仓库镜像才是我们可以用的,端口是我们程序占用的端口。那证书与密钥又是用来开启https。
每个参数进行设置详细的类型说明。
e := echo.New()创建一个名叫e的实例。
e.use是使用将中间件添加到在路由器之后运行的链中,也就是说这里是自定义log输出。
具体解释参考https://studygolang.com/articles/11740
2.2 LoggerWithConfig是这么含义?
它来自github.com/labstack/echo/middleware,通过查找,在https://github.com/labstack/echo/blob/master/middleware/logger.go文件中。描述功能为一个中间件配置的日志。如下:
type ( // LoggerConfig defines the config for Logger middleware. LoggerConfig struct { // Skipper defines a function to skip middleware. Skipper Skipper Format string `yaml:"format"` // Optional. Default value DefaultLoggerConfig.CustomTimeFormat. CustomTimeFormat string `yaml:"custom_time_format"` // Output is a writer where logs in JSON format are written. // Optional. Default value os.Stdout. Output io.Writer template *fasttemplate.Template colorer *color.Color pool *sync.Pool } ) var ( // DefaultLoggerConfig is the default Logger middleware config. DefaultLoggerConfig = LoggerConfig{ Skipper: DefaultSkipper, Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` + `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` + `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` + `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", CustomTimeFormat: "2006-01-02 15:04:05.00000", colorer: color.New(), } ) // LoggerWithConfig returns a Logger middleware with config. // See: `Logger()`. func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { // Defaults if config.Skipper == nil { config.Skipper = DefaultLoggerConfig.Skipper } if config.Format == "" { config.Format = DefaultLoggerConfig.Format } if config.Output == nil { config.Output = DefaultLoggerConfig.Output } config.template = fasttemplate.New(config.Format, "${", "}") config.colorer = color.New() config.colorer.SetOutput(config.Output) config.pool = &sync.Pool{ New: func() interface{} { return bytes.NewBuffer(make([]byte, 256)) }, }
middleware.LoggerConfig{Format: "method=${method}, uri=${uri}, status=${status}\n",}等于config LoggerConfig
config.Skipper并没有明确的定义,但这里是给予了默认值DefaultLoggerConfig.Skipper即defaultSkipper,暂认为是一种配置标识。
config.Format给出了输出选择属性,即method=${method}, uri=${uri}, status=${status}\n
config.Output默认输出一种json格式。
当我们推理到这里的时候,就会发现我们又要开启对各个莫名其妙的调用包的理解之旅了,因为它是我们的成长之路。
首先我们先明确一下LoggerWithConfig函数传入了什么?返回了什么?以什么格式返回输出?
传入了什么?config LoggerConfig代码已写出。
返回了什么?bytes.NewBuffer(make([]byte, 256))
- 以什么格式返回输出?
echo.MiddlewareFunc
type Context interface { Request() *http.Request SetRequest(r *http.Request) Response() *Response IsTLS() bool IsWebSocket() bool Scheme() string RealIP() string Path() string SetPath(p string) Param(name string) string ParamNames() []string SetParamNames(names ...string) ParamValues() []string SetParamValues(values ...string) QueryParam(name string) string QueryParams() url.Values QueryString() string FormValue(name string) string FormParams() (url.Values, error) FormFile(name string) (*multipart.FileHeader, error) MultipartForm() (*multipart.Form, error) Cookie(name string) (*http.Cookie, error) SetCookie(cookie *http.Cookie) Cookies() []*http.Cookie Get(key string) interface{} Set(key string, val interface{}) Bind(i interface{}) error Validate(i interface{}) error Render(code int, name string, data interface{}) error HTML(code int, html string) error HTMLBlob(code int, b []byte) error String(code int, s string) error JSON(code int, i interface{}) error JSONPretty(code int, i interface{}, indent string) error JSONBlob(code int, b []byte) error JSONP(code int, callback string, i interface{}) error JSONPBlob(code int, callback string, b []byte) error XML(code int, i interface{}) error XMLPretty(code int, i interface{}, indent string) error XMLBlob(code int, b []byte) error Blob(code int, contentType string, b []byte) error Stream(code int, contentType string, r io.Reader) error File(file string) error Attachment(file string, name string) error Inline(file string, name string) error NoContent(code int) error Redirect(code int, url string) error Error(err error) Handler() HandlerFunc SetHandler(h HandlerFunc) Logger() Logger Echo() *Echo Reset(r *http.Request, w http.ResponseWriter) }
NewBuffer使用buf作为它的初始内容来创建和初始化一个新的Buffer。新的Buffer获得了buf的所有权,调用者在调用之后不应该使用buf。NewBuffer用于准备一个Buffer来读取现有数据。它还可以用于设置用于写入的内部缓冲区的初始大小。要做到这一点,buf应该具有所需的容量,但长度为零。
再次,我们分析LoggerWithConfig的过程
2.2.1 fasttemplate.New(config.Format, "${", "}")是什么?
来自https://github.com/valyala/fasttemplate/blob/master/template.go
New使用给定的startTag和endTag作为标签开始和结束来解析给定的模板。返回的模板可以通过使用Execute*方法并发运行goroutines来执行。如果给定的模板无法解析,则会出现新的恐慌。如果模板可能包含错误,请使用NewTemplate。
config.Format即是template
"${", "}"即是开始结束标识符
reset方法中重定义t结构体的变量名后,利用 unsafeString2Bytes()对各个变量进行了处理,那么它的逻辑什么?
在fasttemplate包中的unsafe.go代码我们找到了。
这里用到了reflect包和unsafe包,他们的作用分别是:
reflect:反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。
unsafe: 指向不同类型数据的指针,是无法直接相互转换的,必须借助unsafe.Pointer
StringHeader是字符串的运行时表示。它不能安全或便携地使用,它的表示形式可能会在以后的版本中改变。而且,Data字段不足以保证它引用的数据不会被垃圾收集,因此程序必须保持一个独立的、正确键入的指向底层数据的指针.
Uintptr是一个整数类型,它大到足以容纳任何指针的位模式
len当然是整数类型
SliceHeader是片的运行时表示。它不能安全或便携地使用,它的表示形式可能会在以后的版本中改变。而且,Data字段不足以保证它引用的数据不会被垃圾收集,因此程序必须保持一个独立的、正确键入的指向底层数据的指针。
unsafeString2Bytes其实就是将字符串的格式转换为切片类型,而go格式转换需要unsafe.Point方法。
分析到这里,我们要倒推一下s是什么?
unsafeString2Bytes(s string)---->unsafeString2Bytes(template)----template是Template的结构体一部分----->New(template, startTag, endTag string)------------>fasttemplate.New(config.Format, "${", "}")---->LoggerWithConfig(config LoggerConfig) ------>iddleware.LoggerWithConfig(middleware.LoggerConfig{
Format: “method=m e t h o d , u r i = {method}, uri=method,uri={uri}, status=${status}\n”,
})
template即是Format: "method=${method}, uri=${uri}, status=${status}\n"
回到fasttemplate代码中的reset函数中,继续往下分析。
当s、a、b都转换为切片格式之后,利用bytes.count()方法统计s中a的数量。
随后,对t.texts与t.tags初始化切片分配tagcount内存容量。
判断s中是否有a起始标志并返回它的位置,如果为空不存在,即返回-1,当<0时,直接将s合并给t.texts格式并退出。
如果n>0,把s中第一个a之前的切片输出给t.texts
s = s[n+len(a):]则是重定义s中出现a之后的切片重新赋值给s
然后判断s中是否有b结尾标志的数量,假如没有,则报错。因为有起始必有结尾才行。
append(t.tags, unsafeBytes2String(s[:n]))是把起始到结尾之间的切片赋值给t.tags,
最后s再次重新定义为s中b之后的切片内容。
回到包含reset函数的NewTemplate函数中看,NewTemplate其实是通过"${"与"}"对template格式进行一次检验,因为reset没有确切的返回值,只是返回是否报错而已,但是reset设定了t结构体的属性值内容,虽然没有在NewTemplate返回输出,但包含NewTemplate函数的New函数输出了。t结构体包含的template、startTag、endTag、texts、tags 都有了具体内容。
new函数返回输出在echo.middleware代码中LoggerWithConfig函数有体现,给了config.template,即属于*fasttemplate.Template类型,是原来t的同一个结构体而已。
我们继续分析LoggerWithConfig。



















