what is Jenkins
Jenkins是一款由Java编写的开源的持续集成工具。
what is continuous integration
根据 ThoughtWorks 的 Martin Fowler 的观点,持续集成(continuous
integration)是一种软件开发实践,要求团队成员经常集成他们的工作。开发人员每次代码合并都会触发持续集成服务器进行自动构建,这个过程包括了编译、单元测试、集成测试、质量分析等步骤,通过自动化的过程进行验证,以尽快检测集成错误。这种方法会使得集成问题大幅减少,更快地实现有凝聚力的软件开发。
业界普遍认同的持续集成的原则包括:
1)需要版本控制软件保障团队成员提交的代码不会导致集成失败。常用的版本控制软件有
Git、CVS、Subversion 等;
2)开发人员必须及时向版本控制库中提交代码,也必须经常性地从版本控制库中更新代码到本地;
3)需要有专门的集成服务器来执行集成构建。根据项目的具体实际,集成构建可以被软件的修改来直接触发,也可以定时启动,如每半个小时构建一次;
4)必须保证构建的成功。如果构建失败,修复构建过程中的错误是优先级最高的工作。一旦修复,需要手动启动一次构建。
Jenkins对持续集成的支持
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的方式和源码放在一起
比起shell,代码更强大,健壮,容易复用,可以以面向对象的方式进行开发、官方提供了大量与部署相关的方法、关键字
可以在发布过程中有更多控制手段
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
来解决这些问题是一个非常好的办法,我们并不需要重新实现解释器,也可以利用宿主语言的抽象能力。