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.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
來解決這些問題是一個非常好的辦法,我們並不需要重新實現直譯器,也可以利用宿主語言的抽象能力。