Jenkins And DSL

what is Jenkins

Jenkins Logo

Jenkins是一款由Java编写的开源的持续集成工具。

what is continuous integration

根据 ThoughtWorks 的 Martin Fowler 的观点,持续集成(continuous
integration)是一种软件开发实践,要求团队成员经常集成他们的工作。开发人员每次代码合并都会触发持续集成服务器进行自动构建,这个过程包括了编译、单元测试、集成测试、质量分析等步骤,通过自动化的过程进行验证,以尽快检测集成错误。这种方法会使得集成问题大幅减少,更快地实现有凝聚力的软件开发。

马丁福勒

业界普遍认同的持续集成的原则包括:

1)需要版本控制软件保障团队成员提交的代码不会导致集成失败。常用的版本控制软件有
Git、CVS、Subversion 等;

2)开发人员必须及时向版本控制库中提交代码,也必须经常性地从版本控制库中更新代码到本地;

3)需要有专门的集成服务器来执行集成构建。根据项目的具体实际,集成构建可以被软件的修改来直接触发,也可以定时启动,如每半个小时构建一次;

4)必须保证构建的成功。如果构建失败,修复构建过程中的错误是优先级最高的工作。一旦修复,需要手动启动一次构建。

流程图.png

Jenkins对持续集成的支持

Jenkins构建触发
Pipeline对部署的支持

what is DSL

Domain Specific Language 专门针对 一个特定的问题领域
含有建模所需的语法和语义,在与问题域相同的抽象层次对概念建模

DSL示例

pipeline

           stage('docker build') {
                steps {
                    //生成代码镜像1
                    script {
                        customImage = docker.build(docker_tag)
                    }
                }
            }
            stage('push image') {
                steps {
                    script {
                        docker.withRegistry("https://harbor.xxx.xxx", 'harbor_xxx_net') {
                            customImage.push()
                        }
                    }
                }
            }

            stage('deploy') {
                steps {
                    script {
                        deployK8s config.GROUP, config.PROJECT_NAME, IMAGE_TAG, params.TARGET, config.TOKEN, true
                    }
                }
            }

geb

import geb.Browser

Browser.drive {
    go "http://myapp.com/login"

    assert $("h1").text() == "Please Login"

    $("form.login").with {
        username = "admin"
        password = "password"
        login().click()
    }

    assert $("h1").text() == "Admin Section"
}

gradle

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

why Jenkins need pipeline

pipeline可以进入版本控制,以jenkinsfile的方式和源码放在一起


Jenkinsfile

比起shell,代码更强大,健壮,容易复用,可以以面向对象的方式进行开发、官方提供了大量与部署相关的方法、关键字

Jenkins-library

可以在发布过程中有更多控制手段

Input-demo

pipeline的基础概念

Stage:
一个Pipeline可以划分为若干个Stage,每个Stage代表一组操作。注意,Stage是一个逻辑分组的概念,可以跨多个Node。

Node:
一个Node就是一个Jenkins节点,或者是Master,或者是Agent,是执行Step的具体运行期环境。

Step:
Step是最基本的操作单元,小到创建一个目录,大到构建一个Docker镜像,由各类Jenkins
Plugin提供。

pipeline in Groovy

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'make'
            }
        }
        stage('Test'){
            steps {
                sh 'make check'
                junit 'reports/**/*.xml'
            }
        }
        stage('Deploy') {
            steps {
                sh 'make publish'
            }
        }
    }
}

DSL的种类

外部DSL(external DSL)
在主程序语言之外,用一种单独的语言表示领域专用语言,是从零开发的DSL,在词法分析、解析技术、解释、编译、代码生成等方面拥有独立的设施。开发外部DSL近似于从零开始实现一种拥有独特语法和语义的全新语言(markdown、xml、html)
内部DSL(internal DSL)
将现有的编程语言作为宿主语言,基于其设施建立专门面向特定领域的各种语义。(rails,gradle,pipeline)

Groovy适合用来构建DSL的原因

方法调用的语法

println('demo')
println 'demo'

运算符重载

操作符重载

强大的闭包,函数当作第一类对象


class PizzaShop {
    def config = [:]

    def static order(closure) {
        PizzaShop shop = new PizzaShop()
        shop.with closure
    }

    def size(theSize) { println "size is $theSize" }

    def toppings(String[] theToppings) { println "Toppings received $theToppings" }

    def methodMissing(name, args) {

    }
}

PizzaShop.order {
    size 'Large'
    toppings 'Olives', 'Bell Pepper', 'Onions'
}

强大的元编程能力


use(groovy.time.TimeCategory) {
    //直接用数字的写法
    println 1.minute.from.now //一分钟以后
    println 30.days.ago   //30天前的时间

    // 还可以与日期型的混用
    def someDate = new Date()
    println someDate - 3.months //三个月前的时间
}

Integer.metaClass.getDays { ->
    delegate
}

Integer.metaClass.getAgo { ->
    def today = Calendar.instance
    today.add(Calendar.DAY_OF_MONTH, -delegate)
    today
}

GregorianCalendar.metaClass.at { Double time ->
    def timeDbl = time.doubleValue()
    def hours=(int)timeDbl
    def minutes=(int)((timeDbl-hours)*100)
    delegate.set(Calendar.HOUR_OF_DAY,hours)
    delegate.set(Calendar.MINUTE,minutes)
    delegate.time
}

println 2.days.ago.at(4)

属性赋值的优雅


class Config {
    def map=[:]
    def methodMissing(String name,args){
        this.map[name]=args[0]
        println(args[0])
    }
    private String text;

    public String getMessage() {
        return "GET " + text;
    }

    public void setMessage(final String text) {
        this.text = "SET " + text
    }
}

config=new Config()
config.name "John"

println config.map['name']

def s2 = new Config(message: 'Groovy constructor')  // Named parameter in constructor.
assert 'GET SET Groovy constructor' == s2.getMessage()

def s3 = new Config()
s3.message = 'Groovy style'  // = assigment for property.
assert 'GET SET Groovy style' == s3.message  // get value with . notation.

可控的DSL沙盒


dsl = new File('./deploy.dsl')
def g = new GeneralBuildXml(xml)
def binding = new Binding()
binding.setProperty('exclude', new MethodClosure(g, 'exclude'))
binding.setProperty("out", new PrintWriter(new StringWriter()))
CompilerConfiguration conf = new CompilerConfiguration();
SecureASTCustomizer customizer = new SecureASTCustomizer();
customizer.with {
    closuresAllowed = true // 用户能写闭包
    methodDefinitionAllowed = true // 用户能定义方法
    importsWhitelist = [] // 白名单为空意味着不允许导入
    tokensWhitelist = [
            PLUS,
            EQUAL
    ].asImmutable()
    //将用户所能定义的常量类型限制为数值类型
    constantTypesClassesWhiteList = [
            String.class,
            Object.class,
    ].asImmutable()
}
customizer.setReceiversWhiteList(Arrays.asList(
        "java.lang.Object"
));
conf.addCompilationCustomizers(customizer);
new GroovyShell(binding, conf).evaluate(dsl)

超出沙盒允许的调用将会失败


exclude {
    dir "test"+"1", ".idea"
    file "composer.lock"
}
new File('demo').write('test')
java.lang.SecurityException: Method calls not allowed on [java.io.File]
    at org.codehaus.groovy.control.customizers.SecureASTCustomizer$SecuringCodeVisitor.visitMethodCallExpression(SecureASTCustomizer.java:924)
    at org.codehaus.groovy.ast.expr.MethodCallExpression.visit(MethodCallExpression.java:70)
    at org.codehaus.groovy.control.customizers.SecureASTCustomizer$SecuringCodeVisitor.visitExpressionStatement(SecureASTCustomizer.java:846)
    at org.codehaus.groovy.ast.stmt.ExpressionStatement.visit(ExpressionStatement.java:42)
    at org.codehaus.groovy.control.customizers.SecureASTCustomizer$SecuringCodeVisitor.visitBlockStatement(SecureASTCustomizer.java:806)
    at org.codehaus.groovy.ast.stmt.BlockStatement.visit(BlockStatement.java:71)
    at org.codehaus.groovy.control.customizers.SecureASTCustomizer.call(SecureASTCustomizer.java:616)
    at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1087)
    at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:631)
    at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:609)
    at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:586)
    at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:354)
    at groovy.lang.GroovyClassLoader.access$300(GroovyClassLoader.java:87)
    at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:323)
    at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:320)
    at org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache.getAndPut(ConcurrentCommonCache.java:147)
    at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:318)
    at groovy.lang.GroovyShell.parseClass(GroovyShell.java:547)
    at groovy.lang.GroovyShell.parse(GroovyShell.java:559)
    at groovy.lang.GroovyShell.evaluate(GroovyShell.java:443)
    at groovy.lang.GroovyShell.evaluate(GroovyShell.java:491)
    at groovy.lang.GroovyShell$evaluate.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)
    at main.run(main.groovy:42)
    at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:264)
    at groovy.lang.GroovyShell.run(GroovyShell.java:377)
    at groovy.lang.GroovyShell.run(GroovyShell.java:366)
    at groovy.ui.GroovyMain.processOnce(GroovyMain.java:589)
    at groovy.ui.GroovyMain.run(GroovyMain.java:332)
    at groovy.ui.GroovyMain.access$1400(GroovyMain.java:69)
    at groovy.ui.GroovyMain$GroovyCommand.process(GroovyMain.java:291)
    at groovy.ui.GroovyMain.processArgs(GroovyMain.java:134)
    at groovy.ui.GroovyMain.main(GroovyMain.java:116)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.tools.GroovyStarter.rootLoader(GroovyStarter.java:114)
    at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:136)

1 error

小结

Jenkins在引入pipeline之后变得更加强大和易于维护,也给我们一个启示,当我们在某一个领域经常需要解决重复性问题时,可以考虑实现一个
DSL 专门用来解决这些类似的问题。 而使用嵌入式 DSL
来解决这些问题是一个非常好的办法,我们并不需要重新实现解释器,也可以利用宿主语言的抽象能力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容