jenkins as code 与go语言学习

Posted wiswang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jenkins as code 与go语言学习相关的知识,希望对你有一定的参考价值。

前言

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

所有用的到文件简介

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

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

配置文件

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

- department_name: java
  cn_name: java组
  apps:
    - app_name: app-1
      nodes:
        - node1
        - node2
        - node3
      ansible_playbook_name: deploy-springboot-jar.yml
      jvm_size: 2048m
      template_name: springboot.tpl
    - app_name: app-2
      nodes:
        - node1
        - node2
        - node3
      ansible_playbook_name: deploy-springboot-jar.yml
      jvm_size: 512m
      template_name: springboot.tpl
- department_name: java2
  cn_name: java2组
  apps:
    - app_name: app-4
      nodes:
        - node1
        - node2
        - node3
      ansible_playbook_name: deploy-springboot-war.yml
      jvm_size: 2048m
      template_name: springboot.tpl

GO Template

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

def app_name = ‘{{ .App.AppName }}‘
def gitUrl = "[email protected]:{{ .Name }}/{{ .App.AppName }}.git"
job("{{ .Name }}-${app_name}") {
description()
keepDependencies(false)
parameters {
choiceParam("Mode", ["release", "deploy"], "")
choiceParam("app_name", ["${app_name}"], "")
choiceParam("NODES", {{ ProcessNodes .App.Nodes }}, "")
gitParam(‘VERSION‘) {
sortMode(‘DESCENDING‘)
tagFilter(‘*‘)
}
}
scm {
git {
remote {
url(gitUrl)
credentials("git")
}
branch("$VERSION")
}
}
disabled(false)
concurrentBuild(false)
steps {
maven{
configure { node ->
node / ‘mavenName‘ (‘maven‘)
}
goals(‘clean‘)
}
maven{
configure { node ->
node / ‘mavenName‘ (‘maven‘)
}
goals(‘install -Pproduct‘)
}
shell("""
source /opt/py3/bin/activate

if [ $Mode == release ]
then
START_TASK="copy app code"
fi

echo $JOB_NAME
cd /ansible/
echo $WORKSPACE
for host in $NODES
do
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 }}"
done
""")
}
}
listView("{{ .CnName }}") {
jobs {
regex(/{{ .Name }}-.+/)
}
columns {
status()
weather()
name()
lastSuccess()
lastFailure()
lastDuration()
buildButton()
}
}

GO源码

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

package main

import (
    "io/ioutil"
    "github.com/ghodss/yaml"
    "fmt"
    "text/template"
    "os"
    "log"
    "strings"
    "os/exec"
    "bytes"
)

type Department struct {
    Name string `json:"department_name"`
    CnName string `json:"cn_name"`
    Apps []App `json:"apps"`
}

type App struct {
    AppName string `json:"app_name"`
    Nodes []string `json:"nodes"`
    PythonVersion string `json:"python_version"`
    JvmSize string `json:"jvm_size"`
    TemplateName string `json:"template_name"`
    AnsiblePlaybookName string `json:"ansible_playbook_name"`
    DestPath string `json:"dest_path"`
    TomcatGitUrl string `json:"tomcat_git_url"`
    TomcatPort string `json:"tomcat_port"`
    TomcatAdminPort string `json:"tomcat_admin_port"`
    TomcatName string `json:"tomcat_name"`
    TomcatWebApp string `json:"tomcat_web_app"`

}


func In(i string,l []string) bool{
    for _, item:= range l{
        if item == i{
            return true
        }
    }
    return false
}

func checkErr(err error)  {
    if err != nil{
        log.Fatal(err)
    }
}

func ProcessNodes(l []string) string {
    if len(l) == 1{
        return fmt.Sprintf(`["%s"]`,strings.Join(l," "))
    }
    return fmt.Sprintf(`["%s","%s"]`,l[0],strings.Join(l," "))
}

func AppNameToFileName(appname string) string {
    appname = strings.Replace(appname,"-","_",-1)
    return fmt.Sprintf("%s.groovy",appname)
}


func SysCommand(command string){
    commandList := strings.Fields(command)
    var out bytes.Buffer
    cmd := exec.Command(commandList[0],commandList[1:]...)
    cmd.Stdout = &out
    cmd.Stderr = &out
    err := cmd.Run()
    if err != nil{
        log.Printf("commd error: %v",command)
    }
    log.Println(out.String())
}

func main()  {
    content,err := ioutil.ReadFile("config.yaml")
    checkErr(err)
    var d []Department

    err = yaml.Unmarshal(content, &d)
    checkErr(err)
    DepartmentNames := []string{}
    funcMap := template.FuncMap{
        "ProcessNodes": ProcessNodes,
    }
    for _, department := range d{
        err := os.MkdirAll(department.Name,0755)
        checkErr(err)
        DepartmentNames = append(DepartmentNames, department.Name)

        if AppFileNames := []string{}; department.Apps != nil{
            for _, app := range department.Apps{
                FileName :=  AppNameToFileName(app.AppName)
                AppFileNames = append(AppFileNames,FileName)
                data := map[string]interface{}{
                    "Name":department.Name,
                    "CnName":department.CnName,
                    "App":app,
                }
                templ,err := template.New(app.TemplateName).Funcs(funcMap).ParseFiles(app.TemplateName)
                checkErr(err)
                f, err := os.Create(fmt.Sprintf("%s/%s",department.Name, FileName))
                defer f.Close()
                checkErr(err)
                err = templ.Execute(f, data)
                checkErr(err)
            }
            ExistFiles,err := ioutil.ReadDir(department.Name)
            checkErr(err)
            for _,v := range ExistFiles{
                if ! In(v.Name(),AppFileNames){
                    log.Println("going to delete file: ",v.Name())
                    SysCommand(fmt.Sprintf("git rm %s/%s",department.Name,v.Name()))
                }

            }
        }
    }
    ExistDirs,err := ioutil.ReadDir("./")
    checkErr(err)
    for _,dir := range ExistDirs{
        if dir.IsDir()&& dir.Name()!=".idea" && dir.Name()!=".git" &&  ! In(dir.Name(),DepartmentNames){
            log.Println("going to delete dir: ",dir.Name())
            log.Printf("git rm -r %s
",dir.Name())
            SysCommand(fmt.Sprintf("git rm -r %s",dir.Name()))
            checkErr(err)
        }
    }
    SysCommand("git add * ")
    SysCommand("git commit -m ‘update‘ ")
    //SysCommand("git push ")
    log.Println("finished")
}

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

脚本运行的结果

bogon:jenkins-dsl hongzhi.wang$ go run generate_dsl.go 
bogon:jenkins-dsl hongzhi.wang$ tree
.
├── config.yaml
├── generate_dsl.go
├── java
│   ├── app_1.groovy
│   └── app_2.groovy
├── java2
│   └── app_4.groovy
└── springboot.tpl

剩余的工作

这个脚本把代码推送到gitlab之后,gitlab通过webhook触发一个jenkins-seed-job,这个job再运行groovy脚本,这个groovy脚本里面记得把已经存在的删了,我们是一个部门对应一个jenkins-seed-job,一个webhook。如果config.yaml太大了,可以把一些部门的配置放到另一个源码库里。

steps {
        dsl {
            ignoreExisting(false)
            removeAction("DELETE")
            removeViewAction("IGNORE")
            lookupStrategy("JENKINS_ROOT")
        }
    }

总结

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

以上是关于jenkins as code 与go语言学习的主要内容,如果未能解决你的问题,请参考以下文章

jenkins+kubernetes(k8s)+docker持续集成与部署(CI/CD) - k8s系列

Jenkins构建并部署一个go语言项目

Jenkins构建并部署一个go语言项目

VS Code配置Go语言开发环境

在Jenkins pipeline中融入python和go语言编程

VS Code配置Go语言开发环境(建议使用goland)