DevOps系列学习Drone之drone-runner-exec源码学习
drone 介绍
Drone 是一种基于容器技术的持续交付系统。Drone 使用简单的 YAML 配置文件(docker-compose 的超集)来定义和执行 Docker 容器中的 Pipelines。
Drone 与流行的源代码管理系统无缝集成,包括 GitHub,GitHub Enterprise,Bitbucket 等。
Drone 是一个用 Go 语言开发的基于容器运行的持续集成软件。
Drone是一个Golang技术栈的CI解决方案,功能和Jenkins之类的CI工具类似。
优点
Golang 编写,镜像体积小,搭建容易,运行时占用资源小
支持主流代码托管平台Webhook沟通
构建运行时采用image优先,保证在不同平台的构建结果一致
支持插件化,提供强大的功能支持
现代化UI设计,操作简单明了
缺点
年轻,常改版
官方的各种文档写的太烂了
功能和完善程度不及一些老牌 CI
架构
由 1 台 Server 通过Webhook跟代码托管平台做沟通,
接收到事件后启动Runner来处理 Server 上产生的任务。
Runner可以在同一台主机上,也可以分散在多台不同的主机上。
核心概念
pipeline
pipeline可以帮助完成自动化软件交付过程中的步骤,例如启动代码构建、运行自动化测试以及部署到测试或生产环境。pipeline的执行由源代码储存仓库repository触发。代码更改会触发Webhook从而与Drone沟通,后者便开始运行相应的pipeline。
pipeline的种类不止一种,例如:
Docker:在临时的 Docker 容器中执行命令,保证在不同平台的构建结果一致
exec:直接在主机上执行 shell 命令而不隔离,对于不支持容器(如 macOS)的操作系统和体系结构,此运行程序尤其有用
ssh:使用 ssh 协议在远程服务器上执行 shell 命令
platform
使用platform配置目标操作系统和体系结构,并将pipeline路由到适当的运行器。如果未指定,则系统默认为Linux amd64。
workspace
Drone会自动创建一个临时卷,称为工作区,在其中 clone repository。工作区是管道中每个步骤的当前工作目录。
steps
steps定义为一系列的 shell 命令。这些命令在 git 仓库的根目录(工作区)中执行,工作区由管道中的所有步骤共享。
一个特定的steps由多个step组成,供pipeline执行,例如:
clone
安装依赖、单元测试、生成静态文件、拷贝静态文件
condition
condition决定了当前step的触发条件。
触发条件有多种,例如:
根据 Branch:e.g. master/beta
根据 Event:e.g. push/pull_request
根据 Reference:e.g. refs/heads/feature-*
根据 Repository:e.g. octocat/hello-world
根据 Instance:e.g. drone.instance1.com/drone.instance2.com
根据 Status:e.g. success/failure
更多请参考文档
trigger
trigger决定了当前pipeline的触发条件。
触发条件有多种,基本和condition一致。
结合 .drone.yml 使用
1 | --- |
drone-runner-exec
上面说到 pipeline 类型有多种,我们这里先选择 exec 这种类型来学习,了解其中的源码是怎么实现的。
学习他是怎么解析yml文件的,我们能不能抽离出来部分代码实现个简单的执行本地yml文件的工具??
源码地址:github.com/golang108/drone-runner-exec
main 入口
1 | func main() { |
Command
1 | func Command() { |
这里注册的几个子命令,这里使用了 kingpin 这个包来解析命令行的。
registerCompile 这个 子命令是用来 编译yml 为 json 文件的?? 待定
registerExec 这个就是执行本地的yml文件的 ,我们主要就学习这里的源码
command/exec.go -> registerExec
1 |
|
这个函数就是 初始化 命令行 子命令 exec的。 这样就可以在命令行中这么执行:
1 |
|
默认什么参数都不带,就是显示帮助信息。从中可以看到3个子命令,help子命令 是 自带的现实帮助的。
compile 是 我们注册的一个 通过 registerCompile() 注册的。
exec 就是我们这里要讲的的。
kingpin 解析命令参数具体怎么用就不展开了。
我们比较关心的是 执行了 exec 这个子命令,入口 函数会 调哪个呢?
1 | c := new(execCommand) |
通过上面的 代码可以看到 入口 是 调用了 c.run 这个函数的。execCommand 是个struct,是我们定义的 exec 这个子命令对应的 结构体。 这个结构体实现了 run 函数。
这个run函数 是 type Action func(*ParseContext) error
这种类型的,
所以可以给到 Action(c.run)
command/exec.go -> run
具体的exec子命令的实现逻辑 就都在这个run函数里面了
1 | 具体代码 见 仓库的 command/exec.go 文件的 run 函数 |
主要的步骤
读取 yml文件, 解析yml文件
初始化一些变量,环境变量等
执行pipeline中的step
读取 yml文件
1
2rawsource, err := io.ReadAll(c.Source)
c.Source 是命令行参数传入的,c 就是 execCommand 结构体对象
解析yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14// parse and lint the configuration.这里比较关键,从yml文件解析为 manifest 对象
manifest, err := manifest.ParseString(config)
if err != nil {
return err
}
// a configuration can contain multiple pipelines.
// get a specific pipeline resource for execution.
// 当前需要执行的 pipline 是从 manifest 列表中 根据name获取 到的
// name 如果没匹配上就报错,资源没找到, 可以通过选项 --stage-name
resource, err := resource.Lookup(c.Stage.Name, manifest)
if err != nil {
return err
}主要的 解析 动作都在
manifest.ParseString(config)
中。
代码 在 https://github.com/golang108/drone-runner 仓库的 manifest/parse.go
1 | //第一步解析 yml 文件 最外层的 几个属性, 对应到 RawResource 类型里面的值 |
第一个 ParseRaw(r)
主要就是解析 最外层的 几个属性, 对应到 RawResource 类型里面的值
调用 yaml.Unmarshal(resource.Data, resource)
反序列化出来实例对象, 要看能解析哪些
就要看 RawResource 的类型结构体怎么定义的了。
1 | // RawResource is a raw encoded resource with the common |
// Pipeline is a pipeline resource that executes pipelines
// on the host machine without any virtualization.
Pipeline struct {
Version string `json:"version,omitempty"`
Kind string `json:"kind,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Deps []string `json:"depends_on,omitempty"`
Clone manifest.Clone `json:"clone,omitempty"`
Platform manifest.Platform `json:"platform,omitempty"`
Trigger manifest.Conditions `json:"conditions,omitempty"`
Workspace manifest.Workspace `json:"workspace,omitempty"`
Steps []*Step `json:"steps,omitempty"`
}
1 |
|
// compile the pipeline to an intermediate representation.
comp := &compiler.Compiler{
Pipeline: resource, // 这个是通过yml中name获取的那个
Manifest: manifest, // 这里面保存了全部的resource
Build: c.Build,
Netrc: nil, // c.Netrc, // 新建 netrc 文件。这个莫名其妙的 这里直接干掉
Repo: c.Repo,
Stage: c.Stage,
System: c.System,
Environ: c.Environ,
Secret: secret.StaticVars(c.Secrets),
Root: c.Root, // 这个来自命令行
}
spec := comp.Compile(nocontext)
1 |
|
err = runtime.NewExecer(
pipeline.NopReporter(),
console.New(c.Pretty),
engine.New(),
c.Procs,
).Exec(ctx, spec, state)
if err != nil {
return err
}
最后怎么执行的 先是 Execer中的 Exec函数
-> exec函数
-> e.engine.Run(ctx, spec, copy, wc) 函数 == engine/exec.go中的 Run函数