Lightweight
Developer
Provisioning with
Gradle
and SEU-as-code
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 1
About me
Mario-Leander Reimer
Chief Technologist, QAware GmbH
mario-leander.reimer@qaware.de
twitter://@LeanderReimer
http://speakerdeck.com/lreimer
http://github.com/lreimer
http://github.com/seu-as-code
http://seu-as-code.io
http://www.qaware.de
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 2
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 3
SEU: a German acronym;
software development environment
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 4
Definition of Software Industrialization
• This has nothing to do with cheap labor!
• Automation of repetitive and laborious tasks
• Better software quality through standardized,
streamlined tooling
• Well integrated tool chain leads to a higher
productivity and happiness of your team
• Better cost efficiency and competitiveness
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 5
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 6
The ideal.
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 7
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 8
The reality?
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 9
What are the problems?
• Manual and error prone creation and update
procedure of individual SEUs
• Individual SEUs produce different results and
strange bugs
• Old versions of a SEU can't easily be restored
• The SEU for a new project is often a copy of a
previous project. ! Broken windows! !
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 10
The solution ...
• Use a build tool for the automated creation and
update of a SEU
• Software packages are expressed as dependencies
• Gradle tasks and Groovy are used instead of
shell scripting
• Everything is version controlled just like
ordinary source code
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 11
Demo.
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 12
The most minimalistic SEU
plugins {
id 'de.qaware.seu.as.code.base' version '2.4.0'
}
seuAsCode {
seuHome = 'J:'
projectName = 'JavaOne 2016'
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 13
Multi platform SEUs for mixed teams
plugins {
id 'de.qaware.seu.as.code.base' version '2.4.0'
}
import static de.qaware.seu.as.code.plugins.base.Platform.isMac
seuAsCode {
// use closure or Elvis operator (no syntax highlighting :(
seuHome = { if (isMac()) '/Volumes/JavaOne-2016' else 'J:' }
projectName = 'JavaOne 2016'
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 14
Software packages are dependencies
• Gradle has built-in support for dependencies
• Gradle supports different artifact repositories
(Maven, Ivy, Directory)
• Transitive dependencies between software
packages are supported
• Use dependency configurations to determine
installation directory
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 15
Software dependencies and configurations
repositories {
maven { url 'https://dl.bintray.com/seu-as-code/maven' }
jcenter()
}
dependencies {
seuac 'org.codehaus.groovy.modules.scriptom:scriptom:1.6.0'
seuac 'com.h2database:h2:1.4.188'
home 'de.qaware.seu.as.code:seuac-home:2.4.0'
software 'de.qaware.seu.as.code:seuac-environment:2.4.0:jdk8'
software 'org.gradle:gradle:2.14.1'
software "net.java:openjdk8:8u40:$osClassifier"
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 16
Secure passwords with Credentials plugin
plugins { id 'de.qaware.seu.as.code.credentials' version '2.4.0' }
repositories {
maven {
url 'https://your.company.com/nexus/repo'
credentials {
// access stored credentials via extra property
username project.credentials['Nexus'].username
password project.credentials['Nexus'].password
}
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 17
Platform plugin for OS specific builds
plugins { id 'de.qaware.seu.as.code.platform' version '1.0.0' }
platform {
win {
task helloSeuAsCode(group: 'Example') << {
println 'Hello SEU-as-code on Windows.'
}
}
mac {
task helloSeuAsCode(group: 'Example') << {
println 'Hello SEU-as-code on MacOS.'
}
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 18
Manage sources with Git or SVN plugin
plugins {
id 'de.qaware.seu.as.code.credentials' version '2.4.0'
id 'de.qaware.seu.as.code.git' version '2.3.0'
}
git {
plugins {
url 'https://github.com/seu-as-code/seu-as-code.plugins.git'
directory file("$seuHome/codebase/plugins/")
username project.credentials['Github'].username
password project.credentials['Github'].password
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 19
Manage sources with Git or SVN plugin
plugins {
id 'de.qaware.seu.as.code.credentials' version '2.4.0'
id 'de.qaware.seu.as.code.svn' version '2.1.1'
}
subversion {
usersGuide {
url 'https://github.com/seu-as-code/seu-as-code.users-guide.git'
directory file("$seuHome/docbase/users-guide/")
username project.credentials['Subversion'].username
password project.credentials['Subversion'].password
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 20
Implement scripts using plain Gradle
• Gradle build scripts are code! Be creative.
• Gradle provides useful predefined tasks: Copy,
Exec, Sync, Delete, ...
• Gradle tasks can easily reuse Ant tasks.
• Gradle tasks can be implemented in Groovy code.
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 21
Example 1: Init and delete Derby DB
ext.derbyHome = System.env['DERBY_HOME']
// import SQL file via the provided Derby command line tool
task initDemoDb(type: Exec, group: 'Database') {
workingDir "$derbyHome/bin"
commandLine 'cmd', '/B', '/C', 'ij.bat', "$rootDir/scripts/init.sql"
}
task deleteDemoDb(type: Delete, group: 'Database') {
delete "$derbyHome/demo"
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 22
Example 1: The Groovy SQL way
import groovy.sql.*
task initDemoDb(group: 'Database') << {
def derby = [
url: 'jdbc:derby://localhost:1527/demo;create=true',
driver: 'org.apache.derby.jdbc.ClientDriver',
user: 'admin',
password: 'secret'
]
Sql.withInstance(derby) {
execute file("$rootDir/scripts/database/init.sql").text
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 23
Example 2: Restore Solr index data
ext.cores = ['de_DE', 'en_GB', 'zh_CN']
task solrRestoreAll(group: 'Solr') { }
cores.each { coreName ->
def name = coreName.replaceAll("_", "").capitalize()
task "solrRestore${name}"(type: Sync, group: 'Solr') {
from zipTree("$rootDir/scripts/solr/${coreName}.zip")
into "$seuHome/software/solr-4.7.2/example/solr/${coreName}"
}
solrRestoreAll.dependsOn "solrRestore${name}"
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 24
Don't use shell
scripts. Use
Gradle instead!
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 25
Building software packages is easy
• Software packages are essentially plain JAR
files (software + customizations)
• 26 packages available via a Bintray repository
https://bintray.com/seu-as-code/maven
• 44 package build blue-prints are available at
Github, continuously growing.
• Software packages can be build with Gradle!
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 26
Let's build our
first package
using Gradle!
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 27
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 28
Download the original binary distribution
plugins {
id 'de.undercouch.download' version '1.2'
}
import de.undercouch.gradle.tasks.download.Download
task downloadArchive(type: Download) {
src 'https://services.gradle.org/distributions/gradle-3.0-all.zip'
dest "$buildDir"
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 29
Unpack archive using simple Gradle task
task extractArchive(type: Copy, dependsOn: downloadArchive) {
from {
zipTree("$buildDir/${project.name}-${version}-all.zip")
}
into "$buildDir/files"
}
// Best practice: directory name matches the package name
// optionally rename the extracted directory if required
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 30
Customize with additional files and hooks
// plain Groovy, place as a file under META-INF/hooks/
import org.codehaus.groovy.scriptom.*
Scriptom.inApartment
{
def wshShell = new ActiveXObject("WScript.Shell")
def shortcut = wshShell.CreateShortcut("$seuHomegradle.lnk")
shortcut.TargetPath = "${seuLayout.software}go-gradle.bat"
shortcut.WorkingDirectory = "${seuLayout.codebase}"
shortcut.Save()
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 31
Repackage all files as a normal JAR file
task buildPackage(type: Jar, dependsOn: extractArchive) {
baseName project.name
version version
extension 'jar'
// classifier 'x86'
destinationDir buildDir
from "$buildDir/files" // the extracted files
from "files" // some extra files
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 32
Publish JAR artifact to a repository server
publishing {
publications {
gradle(MavenPublication) {
artifact "${buildDir}/${project.name}-${version}.jar"
}
}
repositories {
maven {
// alternatively, use your private company repository server
url 'https://bintray.com/seu-as-code/maven'
credentials { ... }
}
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 33
What's next?
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 34
More features to come ...
• Continuously add more software packages
• Create plugin to build software packages
• Support for self-downloading packages
• Add support for other package managers
• Add Vault support for Credentials plugin
• Improve documentation and user’s guide
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 35
Star SEU-as-code on Github!
$ git clone https://github.com/seu-as-code/seu-as-code.archetype.git
$ git clone https://github.com/seu-as-code/seu-as-code.plugins.git
$ git clone https://github.com/seu-as-code/seu-as-code.packages.git
$ git clone https://github.com/seu-as-code/seu-as-code.examples.git
$ git clone https://github.com/seu-as-code/seu-as-code.documentation.git
$ git clone https://github.com/seu-as-code/seu-as-code.users-guide.git
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 36
Q & A
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 37

Lightweight developer provisioning with gradle and seu as-code

  • 1.
    Lightweight Developer Provisioning with Gradle and SEU-as-code |JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 1
  • 2.
    About me Mario-Leander Reimer ChiefTechnologist, QAware GmbH mario-leander.reimer@qaware.de twitter://@LeanderReimer http://speakerdeck.com/lreimer http://github.com/lreimer http://github.com/seu-as-code http://seu-as-code.io http://www.qaware.de | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 2
  • 3.
    | JavaOne 2016| task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 3
  • 4.
    SEU: a Germanacronym; software development environment | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 4
  • 5.
    Definition of SoftwareIndustrialization • This has nothing to do with cheap labor! • Automation of repetitive and laborious tasks • Better software quality through standardized, streamlined tooling • Well integrated tool chain leads to a higher productivity and happiness of your team • Better cost efficiency and competitiveness | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 5
  • 6.
    | JavaOne 2016| task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 6
  • 7.
    The ideal. | JavaOne2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 7
  • 8.
    | JavaOne 2016| task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 8
  • 9.
    The reality? | JavaOne2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 9
  • 10.
    What are theproblems? • Manual and error prone creation and update procedure of individual SEUs • Individual SEUs produce different results and strange bugs • Old versions of a SEU can't easily be restored • The SEU for a new project is often a copy of a previous project. ! Broken windows! ! | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 10
  • 11.
    The solution ... •Use a build tool for the automated creation and update of a SEU • Software packages are expressed as dependencies • Gradle tasks and Groovy are used instead of shell scripting • Everything is version controlled just like ordinary source code | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 11
  • 12.
    Demo. | JavaOne 2016| task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 12
  • 13.
    The most minimalisticSEU plugins { id 'de.qaware.seu.as.code.base' version '2.4.0' } seuAsCode { seuHome = 'J:' projectName = 'JavaOne 2016' } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 13
  • 14.
    Multi platform SEUsfor mixed teams plugins { id 'de.qaware.seu.as.code.base' version '2.4.0' } import static de.qaware.seu.as.code.plugins.base.Platform.isMac seuAsCode { // use closure or Elvis operator (no syntax highlighting :( seuHome = { if (isMac()) '/Volumes/JavaOne-2016' else 'J:' } projectName = 'JavaOne 2016' } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 14
  • 15.
    Software packages aredependencies • Gradle has built-in support for dependencies • Gradle supports different artifact repositories (Maven, Ivy, Directory) • Transitive dependencies between software packages are supported • Use dependency configurations to determine installation directory | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 15
  • 16.
    Software dependencies andconfigurations repositories { maven { url 'https://dl.bintray.com/seu-as-code/maven' } jcenter() } dependencies { seuac 'org.codehaus.groovy.modules.scriptom:scriptom:1.6.0' seuac 'com.h2database:h2:1.4.188' home 'de.qaware.seu.as.code:seuac-home:2.4.0' software 'de.qaware.seu.as.code:seuac-environment:2.4.0:jdk8' software 'org.gradle:gradle:2.14.1' software "net.java:openjdk8:8u40:$osClassifier" } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 16
  • 17.
    Secure passwords withCredentials plugin plugins { id 'de.qaware.seu.as.code.credentials' version '2.4.0' } repositories { maven { url 'https://your.company.com/nexus/repo' credentials { // access stored credentials via extra property username project.credentials['Nexus'].username password project.credentials['Nexus'].password } } } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 17
  • 18.
    Platform plugin forOS specific builds plugins { id 'de.qaware.seu.as.code.platform' version '1.0.0' } platform { win { task helloSeuAsCode(group: 'Example') << { println 'Hello SEU-as-code on Windows.' } } mac { task helloSeuAsCode(group: 'Example') << { println 'Hello SEU-as-code on MacOS.' } } } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 18
  • 19.
    Manage sources withGit or SVN plugin plugins { id 'de.qaware.seu.as.code.credentials' version '2.4.0' id 'de.qaware.seu.as.code.git' version '2.3.0' } git { plugins { url 'https://github.com/seu-as-code/seu-as-code.plugins.git' directory file("$seuHome/codebase/plugins/") username project.credentials['Github'].username password project.credentials['Github'].password } } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 19
  • 20.
    Manage sources withGit or SVN plugin plugins { id 'de.qaware.seu.as.code.credentials' version '2.4.0' id 'de.qaware.seu.as.code.svn' version '2.1.1' } subversion { usersGuide { url 'https://github.com/seu-as-code/seu-as-code.users-guide.git' directory file("$seuHome/docbase/users-guide/") username project.credentials['Subversion'].username password project.credentials['Subversion'].password } } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 20
  • 21.
    Implement scripts usingplain Gradle • Gradle build scripts are code! Be creative. • Gradle provides useful predefined tasks: Copy, Exec, Sync, Delete, ... • Gradle tasks can easily reuse Ant tasks. • Gradle tasks can be implemented in Groovy code. | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 21
  • 22.
    Example 1: Initand delete Derby DB ext.derbyHome = System.env['DERBY_HOME'] // import SQL file via the provided Derby command line tool task initDemoDb(type: Exec, group: 'Database') { workingDir "$derbyHome/bin" commandLine 'cmd', '/B', '/C', 'ij.bat', "$rootDir/scripts/init.sql" } task deleteDemoDb(type: Delete, group: 'Database') { delete "$derbyHome/demo" } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 22
  • 23.
    Example 1: TheGroovy SQL way import groovy.sql.* task initDemoDb(group: 'Database') << { def derby = [ url: 'jdbc:derby://localhost:1527/demo;create=true', driver: 'org.apache.derby.jdbc.ClientDriver', user: 'admin', password: 'secret' ] Sql.withInstance(derby) { execute file("$rootDir/scripts/database/init.sql").text } } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 23
  • 24.
    Example 2: RestoreSolr index data ext.cores = ['de_DE', 'en_GB', 'zh_CN'] task solrRestoreAll(group: 'Solr') { } cores.each { coreName -> def name = coreName.replaceAll("_", "").capitalize() task "solrRestore${name}"(type: Sync, group: 'Solr') { from zipTree("$rootDir/scripts/solr/${coreName}.zip") into "$seuHome/software/solr-4.7.2/example/solr/${coreName}" } solrRestoreAll.dependsOn "solrRestore${name}" } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 24
  • 25.
    Don't use shell scripts.Use Gradle instead! | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 25
  • 26.
    Building software packagesis easy • Software packages are essentially plain JAR files (software + customizations) • 26 packages available via a Bintray repository https://bintray.com/seu-as-code/maven • 44 package build blue-prints are available at Github, continuously growing. • Software packages can be build with Gradle! | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 26
  • 27.
    Let's build our firstpackage using Gradle! | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 27
  • 28.
    | JavaOne 2016| task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 28
  • 29.
    Download the originalbinary distribution plugins { id 'de.undercouch.download' version '1.2' } import de.undercouch.gradle.tasks.download.Download task downloadArchive(type: Download) { src 'https://services.gradle.org/distributions/gradle-3.0-all.zip' dest "$buildDir" } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 29
  • 30.
    Unpack archive usingsimple Gradle task task extractArchive(type: Copy, dependsOn: downloadArchive) { from { zipTree("$buildDir/${project.name}-${version}-all.zip") } into "$buildDir/files" } // Best practice: directory name matches the package name // optionally rename the extracted directory if required | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 30
  • 31.
    Customize with additionalfiles and hooks // plain Groovy, place as a file under META-INF/hooks/ import org.codehaus.groovy.scriptom.* Scriptom.inApartment { def wshShell = new ActiveXObject("WScript.Shell") def shortcut = wshShell.CreateShortcut("$seuHomegradle.lnk") shortcut.TargetPath = "${seuLayout.software}go-gradle.bat" shortcut.WorkingDirectory = "${seuLayout.codebase}" shortcut.Save() } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 31
  • 32.
    Repackage all filesas a normal JAR file task buildPackage(type: Jar, dependsOn: extractArchive) { baseName project.name version version extension 'jar' // classifier 'x86' destinationDir buildDir from "$buildDir/files" // the extracted files from "files" // some extra files } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 32
  • 33.
    Publish JAR artifactto a repository server publishing { publications { gradle(MavenPublication) { artifact "${buildDir}/${project.name}-${version}.jar" } } repositories { maven { // alternatively, use your private company repository server url 'https://bintray.com/seu-as-code/maven' credentials { ... } } } } | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 33
  • 34.
    What's next? | JavaOne2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 34
  • 35.
    More features tocome ... • Continuously add more software packages • Create plugin to build software packages • Support for self-downloading packages • Add support for other package managers • Add Vault support for Credentials plugin • Improve documentation and user’s guide | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 35
  • 36.
    Star SEU-as-code onGithub! $ git clone https://github.com/seu-as-code/seu-as-code.archetype.git $ git clone https://github.com/seu-as-code/seu-as-code.plugins.git $ git clone https://github.com/seu-as-code/seu-as-code.packages.git $ git clone https://github.com/seu-as-code/seu-as-code.examples.git $ git clone https://github.com/seu-as-code/seu-as-code.documentation.git $ git clone https://github.com/seu-as-code/seu-as-code.users-guide.git | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 36
  • 37.
    Q & A |JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 37