前言

最近看jenkins as code这个概念在很多文章中提起,持续交付中八大原则也有把一切都放入版本管理,最近准备把我们公司用的一些jenkins上的job的配置也放到git中,由于https://github.com/jenkinsci/job-dsl-plugin的支持是groovy。我不懂groovy,所以找了一些网上的groovy脚本改了改,并且通过go template实现这个模板,还能练习使用go。

所有用的到文件简介

一共用到3个文件,只有一个模板,如果有多个情景可以多写几个模板

  1. bogon:jenkins-dsl hongzhi.wang$ tree
  2. .
  3. ├── config.yaml #配置文件
  4. ├── generate_dsl.go #go 源码文件
  5. └── springboot.tpl #go template 文件

配置文件

这次准备通过yaml文件实现模板的渲染,配置文件如下,通过yaml来配置文件比较清晰。

  1. - department_name: java
  2. cn_name: java
  3. apps:
  4. - app_name: app-1
  5. nodes:
  6. - node1
  7. - node2
  8. - node3
  9. ansible_playbook_name: deploy-springboot-jar.yml
  10. jvm_size: 2048m
  11. template_name: springboot.tpl
  12. - app_name: app-2
  13. nodes:
  14. - node1
  15. - node2
  16. - node3
  17. ansible_playbook_name: deploy-springboot-jar.yml
  18. jvm_size: 512m
  19. template_name: springboot.tpl
  20. - department_name: java2
  21. cn_name: java2
  22. apps:
  23. - app_name: app-4
  24. nodes:
  25. - node1
  26. - node2
  27. - node3
  28. ansible_playbook_name: deploy-springboot-war.yml
  29. jvm_size: 2048m
  30. template_name: springboot.tpl

GO Template

这里面主要是用到了自定函数ProcessNodes,其他的都是模板的正常用法,还有就是我们的jenkins主要是传参数拉代码,然后通过ansible-playbook实现应用部署。

  1. def app_name = '{{ .App.AppName }}'
  2. def gitUrl = "git@gitlab.wis.com:{{ .Name }}/{{ .App.AppName }}.git"
  3. job("{{ .Name }}-${app_name}") {
  4. description()
  5. keepDependencies(false)
  6. parameters {
  7. choiceParam("Mode", ["release", "deploy"], "")
  8. choiceParam("app_name", ["${app_name}"], "")
  9. choiceParam("NODES", {{ ProcessNodes .App.Nodes }}, "")
  10. gitParam('VERSION') {
  11. sortMode('DESCENDING')
  12. tagFilter('*')
  13. }
  14. }
  15. scm {
  16. git {
  17. remote {
  18. url(gitUrl)
  19. credentials("git")
  20. }
  21. branch("\$VERSION")
  22. }
  23. }
  24. disabled(false)
  25. concurrentBuild(false)
  26. steps {
  27. maven{
  28. configure { node ->
  29. node / 'mavenName' ('maven')
  30. }
  31. goals('clean')
  32. }
  33. maven{
  34. configure { node ->
  35. node / 'mavenName' ('maven')
  36. }
  37. goals('install -Pproduct')
  38. }
  39. shell("""
  40. source /opt/py3/bin/activate
  41. if [ \$Mode == release ]
  42. then
  43. START_TASK="copy app code"
  44. fi
  45. echo \$JOB_NAME
  46. cd /ansible/
  47. echo \$WORKSPACE
  48. for host in \$NODES
  49. do
  50. ansible-playbook --start-at-task="\$START_TASK" {{ .App.AnsiblePlaybookName }} --extra-vars "target_host=\$host workspace=\$WORKSPACE app_name=\$app_name version=\$VERSION jvm_size={{ .App.JvmSize }}"
  51. done
  52. """)
  53. }
  54. }
  55. publishers {
  56. postBuildScripts {
  57. steps {
  58. steps {
  59. shell("""
  60. /ops/scripts/dingding.py {{ .Name }} `git show -s --pretty=%cN` `git tag -ln \$VERSION` \$NODES
  61. """)
  62. }
  63. }
  64. markBuildUnstable(false)
  65. }
  66. }
  67. }
  68. listView("{{ .CnName }}") {
  69. jobs {
  70. regex(/{{ .Name }}-.+/)
  71. }
  72. columns {
  73. status()
  74. weather()
  75. name()
  76. lastSuccess()
  77. lastFailure()
  78. lastDuration()
  79. buildButton()
  80. }
  81. }

GO源码

这个源码主要按照配置文件,把已经去掉的部门目录还有应用groovy脚本给删了,并且推送到远程分支,groovy脚本的名字里面不能有-,因为groovy的脚本名字会解析成java的类名,-是不能放在java语言的类名里面了。所以只能把应用名字都的-转成_

  1. package main
  2. import (
  3. "io/ioutil"
  4. "github.com/ghodss/yaml"
  5. "fmt"
  6. "text/template"
  7. "os"
  8. "log"
  9. "strings"
  10. "os/exec"
  11. "bytes"
  12. )
  13. type Department struct {
  14. Name string `json:"department_name"`
  15. CnName string `json:"cn_name"`
  16. Apps []App `json:"apps"`
  17. }
  18. type App struct {
  19. AppName string `json:"app_name"`
  20. Nodes []string `json:"nodes"`
  21. PythonVersion string `json:"python_version"`
  22. JvmSize string `json:"jvm_size"`
  23. TemplateName string `json:"template_name"`
  24. AnsiblePlaybookName string `json:"ansible_playbook_name"`
  25. DestPath string `json:"dest_path"`
  26. TomcatGitUrl string `json:"tomcat_git_url"`
  27. TomcatPort string `json:"tomcat_port"`
  28. TomcatAdminPort string `json:"tomcat_admin_port"`
  29. TomcatName string `json:"tomcat_name"`
  30. TomcatWebApp string `json:"tomcat_web_app"`
  31. }
  32. func In(i string,l []string) bool{
  33. for _, item:= range l{
  34. if item == i{
  35. return true
  36. }
  37. }
  38. return false
  39. }
  40. func checkErr(err error) {
  41. if err != nil{
  42. log.Fatal(err)
  43. }
  44. }
  45. func ProcessNodes(l []string) string {
  46. if len(l) == 1{
  47. return fmt.Sprintf(`["%s"]`,strings.Join(l," "))
  48. }
  49. return fmt.Sprintf(`["%s","%s"]`,l[0],strings.Join(l," "))
  50. }
  51. func AppNameToFileName(appname string) string {
  52. appname = strings.Replace(appname,"-","_",-1)
  53. return fmt.Sprintf("%s.groovy",appname)
  54. }
  55. func SysCommand(command string){
  56. commandList := strings.Fields(command)
  57. var out bytes.Buffer
  58. cmd := exec.Command(commandList[0],commandList[1:]...)
  59. cmd.Stdout = &out
  60. cmd.Stderr = &out
  61. err := cmd.Run()
  62. if err != nil{
  63. log.Printf("commd error: %v",command)
  64. }
  65. log.Println(out.String())
  66. }
  67. func main() {
  68. content,err := ioutil.ReadFile("config.yaml")
  69. checkErr(err)
  70. var d []Department
  71. err = yaml.Unmarshal(content, &d)
  72. checkErr(err)
  73. DepartmentNames := []string{}
  74. funcMap := template.FuncMap{
  75. "ProcessNodes": ProcessNodes,
  76. }
  77. for _, department := range d{
  78. err := os.MkdirAll(department.Name,0755)
  79. checkErr(err)
  80. DepartmentNames = append(DepartmentNames, department.Name)
  81. if AppFileNames := []string{}; department.Apps != nil{
  82. for _, app := range department.Apps{
  83. FileName := AppNameToFileName(app.AppName)
  84. AppFileNames = append(AppFileNames,FileName)
  85. data := map[string]interface{}{
  86. "Name":department.Name,
  87. "CnName":department.CnName,
  88. "App":app,
  89. }
  90. templ,err := template.New(app.TemplateName).Funcs(funcMap).ParseFiles(app.TemplateName)
  91. checkErr(err)
  92. f, err := os.Create(fmt.Sprintf("%s/%s",department.Name, FileName))
  93. defer f.Close()
  94. checkErr(err)
  95. err = templ.Execute(f, data)
  96. checkErr(err)
  97. }
  98. ExistFiles,err := ioutil.ReadDir(department.Name)
  99. checkErr(err)
  100. for _,v := range ExistFiles{
  101. if ! In(v.Name(),AppFileNames){
  102. log.Println("going to delete file: ",v.Name())
  103. SysCommand(fmt.Sprintf("git rm %s/%s",department.Name,v.Name()))
  104. }
  105. }
  106. }
  107. }
  108. ExistDirs,err := ioutil.ReadDir("./")
  109. checkErr(err)
  110. for _,dir := range ExistDirs{
  111. if dir.IsDir()&& dir.Name()!=".idea" && dir.Name()!=".git" && ! In(dir.Name(),DepartmentNames){
  112. log.Println("going to delete dir: ",dir.Name())
  113. log.Printf("git rm -r %s\n",dir.Name())
  114. SysCommand(fmt.Sprintf("git rm -r %s",dir.Name()))
  115. checkErr(err)
  116. }
  117. }
  118. SysCommand("git add * ")
  119. SysCommand("git commit -m 'update' ")
  120. //SysCommand("git push ")
  121. log.Println("finished")
  122. }

templ,err := template.New(app.TemplateName).Funcs(funcMap).ParseFiles(app.TemplateName)这个之所以这样是因为要传入自定的函数,parsefile的返回值是两个不能直接用Funcs。这个就要详见Stack Overflow上的答案了。

脚本运行的结果

  1. bogon:jenkins-dsl hongzhi.wang$ go run generate_dsl.go
  2. bogon:jenkins-dsl hongzhi.wang$ tree
  3. .
  4. ├── config.yaml
  5. ├── generate_dsl.go
  6. ├── java
  7.    ├── app_1.groovy
  8.    └── app_2.groovy
  9. ├── java2
  10.    └── app_4.groovy
  11. └── springboot.tpl

剩余的工作

这个脚本把代码推送到gitlab之后,gitlab通过webhook触发一个jenkins-seed-job,这个job再运行groovy脚本,这个groovy脚本运行之后我们线上的 job 就和 YAML 文件描述的一致了,我们是一个部门对应一个jenkins-seed-job,一个webhook。如果config.yaml太大了,可以把一些部门的配置放到另一个源码库里。

  1. steps {
  2. dsl {
  3. ignoreExisting(false)
  4. removeAction("DELETE")
  5. removeViewAction("IGNORE")
  6. lookupStrategy("JENKINS_ROOT")
  7. }
  8. }

更新于2019年3月

最近由于有的组需要频繁发版,所以把部署的权限给了这些组的负责人,但是我们运维又需要知道他们发版的时间和具体原因,所以我们这边的方法是通过 http 调用发送响应的信息到对应的组的钉钉群。调研了一下,直接通过 curl 调用钉钉的接口变量老是传不过去,而且 curl 调用不是很灵活,所以通过在 jenkins 服务器上放置 python 脚本的方式发送钉钉消息,脚本传入的参数分别是组名,git commiter 名字,git tag 和对应 tag 的详细描述,部署的生产服务器,脚本根据组名来分辨具体发送到哪个组。只有构建成功的作业才会发送到钉钉。

  1. publishers {
  2. postBuildScripts {
  3. steps {
  4. steps {
  5. shell("""
  6. /ops/scripts/dingding.py {{ .Name }} `git show -s --pretty=%cN` `git tag -ln \$VERSION` \$NODES
  7. """)
  8. }
  9. }
  10. markBuildUnstable(false)
  11. }
  12. }
  13. }

总结

这次主要是把jenkins的job管理纳入版本控制,并且练习一下go。这里主要是做个笔记o( ̄︶ ̄)o。

重构

最近由于新的需求把上面go的代码重构了一下,如下:

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "github.com/ghodss/yaml"
  6. "io/ioutil"
  7. "log"
  8. "os"
  9. "os/exec"
  10. "strings"
  11. "text/template"
  12. )
  13. const (
  14. ImageTemplatePath = "image_dsl"
  15. )
  16. var (
  17. TemplateName = "common.tpl"
  18. SystemDirs = []string{".git", "image_dsl", ".idea", "templates"}
  19. )
  20. func main() {
  21. SysCommandErrorExit("git pull")
  22. content, err := ioutil.ReadFile("config.yaml")
  23. checkErr(err)
  24. var company Company
  25. err = yaml.Unmarshal(content, &company.Departments)
  26. checkErr(err)
  27. company.JenkinsDsl()
  28. company.CleanInactiveDepartments()
  29. SysCommand("git add * ")
  30. SysCommand("git commit -m 'update' ")
  31. SysCommand("git push ")
  32. log.Println("finished")
  33. }
  34. type Company struct {
  35. Departments []Department `json:"departments"`
  36. }
  37. func (c Company) DepartmentNames() (DepartmentNames []string) {
  38. for _, department := range c.Departments {
  39. DepartmentNames = append(DepartmentNames, department.Name)
  40. }
  41. return
  42. }
  43. func (c Company) JenkinsDsl() {
  44. for _, department := range c.Departments {
  45. department.JenkinsDsl()
  46. }
  47. }
  48. func (c Company) CleanInactiveDepartments() {
  49. ExistDirs, err := ioutil.ReadDir("./")
  50. checkErr(err)
  51. for _, dir := range ExistDirs {
  52. if dir.IsDir() && ! In(dir.Name(), SystemDirs) && ! In(dir.Name(), c.DepartmentNames()) {
  53. log.Println("going to delete dir: ", dir.Name())
  54. log.Printf("git rm -r %s\n", dir.Name())
  55. SysCommand(fmt.Sprintf("git rm -r %s", dir.Name()))
  56. }
  57. }
  58. }
  59. type Department struct {
  60. Name string `json:"department_name"`
  61. CnName string `json:"cn_name"`
  62. Apps []App `json:"apps"`
  63. }
  64. func (d Department) JenkinsDsl() {
  65. d.DepartmentDir()
  66. if d.Apps != nil {
  67. for _, app := range d.Apps {
  68. app.JenkinsDsl(d)
  69. }
  70. d.CleanOfflineApps()
  71. }
  72. }
  73. func (d Department) CleanOfflineApps() {
  74. ExistFiles, err := ioutil.ReadDir(d.Name)
  75. checkErr(err)
  76. for _, v := range ExistFiles {
  77. if ! In(v.Name(), d.AppFileNames()) {
  78. log.Println("going to delete file: ", v.Name())
  79. SysCommand(fmt.Sprintf("git rm %s/%s", d.Name, v.Name()))
  80. }
  81. }
  82. }
  83. func (d Department) AppFileNames() (AppFileNames []string) {
  84. for _, app := range d.Apps {
  85. AppFileNames = append(AppFileNames, AppNameToFileName(app.AppName))
  86. }
  87. return
  88. }
  89. func (d Department) DepartmentDir() {
  90. err := os.MkdirAll(d.Name, 0755)
  91. checkErr(err)
  92. }
  93. type App struct {
  94. AppName string `json:"app_name"`
  95. Department Department `json:"department"`
  96. BuildImage bool `json:"build_image"`
  97. ArtifactFormat string `json:"artifact_format"`
  98. JvmSize string `json:"jvm_size"`
  99. JavaVersion string `json:"java_version"`
  100. Clusters []Cluster `json:"clusters"`
  101. ImageTPL string `json:"image_tpl"`
  102. Values map[string]interface{} `json:"values"`
  103. }
  104. type Cluster struct {
  105. Name string `json:"name"`
  106. ReplicaCount int `json:"replicaCount"`
  107. ReplicaCountMax int `json:"replicaCountMAX"`
  108. }
  109. func (a App) JenkinsDsl(d Department) {
  110. a.Department = d
  111. f := a.file()
  112. err := a.Template().Execute(f, a.TemplateData())
  113. defer f.Close()
  114. checkErr(err)
  115. }
  116. func (a App) ValuesYamlString() (values string) {
  117. Values, err := yaml.Marshal(a.Values)
  118. checkErr(err)
  119. return string(Values)
  120. }
  121. func (a App) TemplateData() (data map[string]interface{}) {
  122. data = map[string]interface{}{
  123. "Name": a.Department.Name,
  124. "CnName": a.Department.CnName,
  125. "App": a,
  126. "Values": a.ValuesYamlString(),
  127. }
  128. return
  129. }
  130. func (a App) Template() (templ *template.Template) {
  131. helmTpl := fmt.Sprintf("templates/helm_dsl/%s", TemplateName)
  132. templ, err := template.New(TemplateName).ParseFiles(helmTpl)
  133. checkErr(err)
  134. return
  135. }
  136. func (a App) file() (*os.File) {
  137. FileName := AppNameToFileName(a.AppName)
  138. f, err := os.Create(fmt.Sprintf("%s/%s", a.Department.Name, FileName))
  139. checkErr(err)
  140. return f
  141. }
  142. func In(i string, l []string) bool {
  143. for _, item := range l {
  144. if item == i {
  145. return true
  146. }
  147. }
  148. return false
  149. }
  150. func checkErr(err error) {
  151. if err != nil {
  152. log.Fatal(err)
  153. }
  154. }
  155. func AppNameToFileName(appname string) string {
  156. appname = strings.Replace(appname, "-", "_", -1)
  157. return fmt.Sprintf("%s.groovy", appname)
  158. }
  159. func SysCommandErrorExit(command string) {
  160. commandList := strings.Fields(command)
  161. var out bytes.Buffer
  162. cmd := exec.Command(commandList[0], commandList[1:]...)
  163. cmd.Stdout = &out
  164. cmd.Stderr = &out
  165. err := cmd.Run()
  166. if err != nil {
  167. log.Fatalln("command error: %v", command)
  168. }
  169. log.Println(out.String())
  170. }
  171. func SysCommand(command string) {
  172. commandList := strings.Fields(command)
  173. var out bytes.Buffer
  174. cmd := exec.Command(commandList[0], commandList[1:]...)
  175. cmd.Stdout = &out
  176. cmd.Stderr = &out
  177. err := cmd.Run()
  178. if err != nil {
  179. log.Printf("command error: %v", command)
  180. }
  181. log.Println(out.String())
  182. }

jenkins as code 与go语言学习的更多相关文章

  1. GO学习-(3) VS Code配置Go语言开发环境

    VS Code配置Go语言开发环境 VS Code配置Go语言开发环境 说在前面的话,Go语言是采用UTF8编码的,理论上使用任何文本编辑器都能做Go语言开发.大家可以根据自己的喜好自行选择.编辑器/ ...

  2. HTML语言学习笔记(会更新)

    # HTML语言学习笔记(会更新) 一个html文件是由一系列的元素和标签组成的. 标签: 1.<html></html> 表示该文件为超文本标记语言(HTML)编写的.成对出 ...

  3. 大一上学期C语言学习心得总结

    经过一个学期的C语言学习,大体算是在这个编程语言上入了门,能够通过一些代码解决特定的问题.当然,每次成功将问题转换成代码都小有激动,虽然只是在黑框上输出了一些数字或是字符串. 编程,虽然还不是很懂,但 ...

  4. 2017-05-4-C语言学习笔记

    C语言学习笔记... ------------------------------------ Hello C语言:什么是程序:程序是指:完成某件事的既定方式和过程.计算机中的程序是指:为了让计算机执 ...

  5. R语言学习 第四篇:函数和流程控制

    变量用于临时存储数据,而函数用于操作数据,实现代码的重复使用.在R中,函数只是另一种数据类型的变量,可以被分配,操作,甚至把函数作为参数传递给其他函数.分支控制和循环控制,和通用编程语言的风格很相似, ...

  6. Go语言学习之路

    我关于Go语言的博客原本发布于我的个人网站:wwww.liwenzhouu.com.但是被某些人抄怕了,没办法只好搬运到博客园. 我的Go语言学习之路 2015年底我因为工作原因接触到了Go语言,那时 ...

  7. Java语言学习day02--6月29日

    Java语言学习day02###01常用的DOS命令 * A: 常用的DOS命令 * a: 打开Dos控制台 * win+r--cmd--回车 * b: 常用dos命令 * cd.. : 退回到上一级 ...

  8. C语言学习 第八次作业总结

    本次作业其实没有新的内容,主要就是复习上一次的一维数组的相关内容.冯老师布置了5道题目,其中涉及到一些比较简单的排序或者是查找的方法.因为数据很少,所以直接使用for循环遍历就可以了. 关于本次作业, ...

  9. C语言学习 第七次作业总结

    C语言学习 第七次作业总结 数组可以分为数组和多下标数组(在传统的国内C语言书本中,将其称为二/多维数组). 数组名称 在之前的课程中,大家应该都有印象,对于int a这样的定义,会为变量 a 声明一 ...

随机推荐

  1. k8s环境清理

    每一种方法 #!/bin/shdocker rm -f $(docker ps -qa)docker volume rm $(docker volume ls -q)cleanupdirs=" ...

  2. oracle 按表数据新增一行

    在功能实现时,能尽量用一个sql语句直接实现业务逻辑的话,就不要去写C#代码,便于维护. 以下sql的逻辑是:给明细表新增一条数据,前提是传入的债券代码存在与债券表,否则不新增.此sql返回受影响行数 ...

  3. HashMap、Hashtable、ConcurrentHashMap的原理与区别(简述)

    HashTable 底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相 ...

  4. Linux 防火墙iptables开放端口

    Iptabels是与Linux内核集成的包过滤防火墙系统,几乎所有的linux发行版本都会包含Iptables的功能.如果 Linux 系统连接到因特网或 LAN.服务器或连接 LAN 和因特网的代理 ...

  5. P3383 【模板】线性筛素数

    因为数据很大所以要用线性筛.. #include<iostream> #include<cstdio> using namespace std; typedef long lo ...

  6. JS Object.defineProperties()方法

    JS Object.defineProperties()方法 描述: Object.defineProperties()方法为目标对象同时配置多个属性. 语法: Object.defineProper ...

  7. 最小化webpack项目

    先把代码贴出来,以后慢慢加说明 参考资料:入门 Webpack,看这篇就够了 / webpack 搭建自动打开,刷新浏览器 一.功能代码1.index.html <!DOCTYPE html&g ...

  8. 数据统计 任务的一点感想 , sql 使用中的坑。

    需求: 多张表(个数不定,需求不是非常明确,只有一个大致需求)根据业务需求统计出一些数据 (按天统计,数据有多条校验规则)进行上传. 注意: 校验数据是否正确是需要第三放来反馈的,而且第三方的测试环境 ...

  9. 基于xposed实现android注册系统服务,解决跨进程共享数据问题

    昨花了点时间,参考github issues 总算实现了基于xposed的系统服务注入,本文目的是为了“解决应用之间hook后数据共享,任意app ServiceManager.getService就 ...

  10. 虚方法(virtual)和抽象方法(abstract)的和接口(interface)的区别

    虚方法(virtual)和抽象方法(abstract)的区别 2017年06月15日 13:41:26 阅读数:65 注:本文转载自 http://www.cnblogs.com/michaelxu/ ...