Jenkins And DSL

作者: brightwang | 来源:发表于2019-03-01 17:03 被阅读8次

    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.skyunion.net", 'harbor_skyunion_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
    来解决这些问题是一个非常好的办法,我们并不需要重新实现解释器,也可以利用宿主语言的抽象能力。

    相关文章

      网友评论

        本文标题:Jenkins And DSL

        本文链接:https://www.haomeiwen.com/subject/iqtjuqtx.html