Take Control. Write a Plugin. Part II

Preview:

DESCRIPTION

Take Control. Write a Plugin. Part II. Baruch Sadogursky JFrog www.jfrog.com. About me. Developer Advocate @JFrog Job definition: Write code Talk about it github.com/ jbaruch @ jbaruch. JFrog & Jenkins. With Jenkins from day 1 Jenkins Artifactory Plugin Hosted JUC Israel - PowerPoint PPT Presentation

Citation preview

Jenkins User Conference San Francisco, Sept 30 2012 #jenkinsconf

Take Control. Write a Plugin.Part II

Baruch SadogurskyJFrog

www.jfrog.com

About me

Developer Advocate @JFrogJob definition:

Write code Talk about it

github.com/jbaruch@jbaruch

With Jenkins from day 1Jenkins Artifactory PluginHosted JUC Israelrepo.jenkins-ci.orgJavaOne DEMOzone

JFrog & Jenkins

Vote and guessingWorking with remote agentsWorking in multiple operation systemsCreating UI using GroovyWriting custom Jelly(?) tagsMaintaining backwards compatibility

Agenda

Vote and guessingWorking with remote agentsWorking in multiple operation systemsCreating UI using GroovyWriting custom Jelly(?) tagsMaintaining backwards compatibility

Agenda

Who saw “Take Control. Write a Plugin” session on YouTube?Let me guess…

one or two hands…

First, let’s vote

PREVIOUSLY IN “TAKE CONTROL. WRITE A PLUGIN”…

“Hello, my name is Noam Tenne”

What you can do with pluginsWhat you can’t do with pluginsPlugins statistics

Overview – Why plugins

UISCMBuild ProcessesSlave managementTooling ... Many, many, more

You can even create new extension points!

What can I extend?

IDEAll majors have good supportWe love IntelliJ IDEA

Build toolCan be Maven or Gradle

Environment

Target: Rewarding failing builds with insulting mockeryGlobal configuration: Motivation phraseProject configuration: Is motivator enabledOutcome: Message appears in log after failure

The “Motivation” Plugin

BACK TO OUR AGENDANowdays…

Vote and guessingWorking with remote agentsWorking in multiple operation systemsCreating UI using GroovyWriting custom Jelly(?) tagsMaintaining backwards compatibility

Agenda

Jenkins has remote agents!

Working with remote agents

Send closures to remote agentshudson.remoting.Callable

Working with remote agents

Java Serialization

Poor guy’s Java closureUsually anonymous inner class (not always)

Closure

1 private static class GetSystemProperties implements Callable<Properties,RuntimeException> { 2 public Properties call() { 3 return System.getProperties(); 4 } 5 private static final long serialVersionUID = 1L; 6 }

Channel?

Cast your bread on the waters

1 this.systemProperties = channel.call(new GetSystemProperties());

Represents a communication channel to the remote peer

Obtain from:

Channel

Where is the file?

Distribution Abstractions – FilePath

hudson.FilePathMuch like java.util.File

Consider pushing logic to the fileUse FilePath.act(FileCallable)

Distribution Abstractions – FilePath

Launch stuff remotely!

Distribution Abstractions – Launcher

hudson.LauncherMuch like java.lang.ProcessBuilder

Pick your environment variables wisely!

Distribution Abstractions – Launcher

Vote and guessingWorking with remote agentsWorking in multiple operation systemsCreating UI using GroovyWriting custom Jelly(?) tagsMaintaining backwards compatibility

Agenda

WORA. You know. But.

/ vs \.sh vs .batQuotes around commandsPermissions

(wait for it…)

Working in multiple OSs

Executing file…

remotely… platform independent…

Running script…

Can You Spot The Error?

1 String workspaceDir = build.getWorkspace().getRemote(); 2 String scriptName = launcher.isUnix() ? "proc.sh" : "proc.bat"; 3 Launcher.ProcStarter procStarter = launcher.launch().stdout(System.out); 4 procStarter.cmds(new File(workspaceDir, scriptName)).join();

Executedlocally!

Use FilePath – it will take care of all the details!Execute FilePath.act(FileCallable)

If you need the File API, invoke() method has it, converted to remote file properly

Going Remote with File

Permissions Dance

1 public boolean isDirectoryReadOnly(final FilePath filePath) throws IOException, 2 InterruptedException { 3 return filePath.getChannel().call(new Callable<Boolean, IOException>() { 4 public Boolean call() throws IOException { 5 Path path = FileSystems.getDefault().getPath(filePath.getRemote()); 6 Set<String> systems = FileSystems.getDefault().supportedFileAttributeViews(); 7 if (systems.contains("dos")) { 8 DosFileAttributeView dosView = 9 Files.getFileAttributeView(path, DosFileAttributeView.class); 10 DosFileAttributes dosAttributes = dosView.readAttributes(); 11 return dosAttributes.isReadOnly(); 12 } 13 if (systems.contains("posix")) { 14 PosixFileAttributeView posixView = 15 Files.getFileAttributeView(path, PosixFileAttributeView.class); 16 PosixFileAttributes posixAttributes = posixView.readAttributes(); 17 Set<PosixFilePermission> permissions = posixAttributes.permissions(); 18 return !permissions.contains(PosixFilePermission.OTHERS_WRITE) 19 } 20 throw new IOException("Unknown filesystem"); 21 } 22 }); 23 }

Vote and guessingWorking with remote agentsWorking in multiple operation systemsCreating UI using GroovyWriting custom Jelly(?) tagsMaintaining backwards compatibility

Agenda

First, let’s look at the docs:

Creating UI using Groovy

Analogous to JellyCan use Jelly tags and libraries

Kohsuke:

Creating UI using Groovy

When What

Lots of program logic Groovy

Lots of HTML layout markup Jelly

Analogous to JellyCan use Jelly tags and libraries

me:

Creating UI using Groovy

When What

Always! Groovy

Jelly:

Groovy:

Creating UI using Groovy

1 <j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form"> 2 <f:section title="Motivation Plugin"> 3 <f:entry title=" Motivating Message" field="motivatingMessage" 4 description="The motivational message to display when a build fails"> 5 <f:textbox/> 6 </f:entry> 7 </f:section> 8 </j:jelly>

1 f=namespace('lib/form') 2 3 f.section(title:'Motivation Plugin') { 4 f.entry(title:'Motivating Message', field:'motivatingMessage', 5 description:'The motivational message to display when a build fails'){ 6 f.textbox() 7 } 8 }

Creating UI using Groovy

1 f=namespace('lib/form') 2 3 f.section(title:'Motivation Plugin') { 4 f.entry(title:'Motivating Message', field:'motivatingMessage', 5 description:'The motivational message to display when a build fails'){ 6 f.textbox() 7 } 8 }

Real codeDebuggable, etc.

(stay tuned…)

Vote and guessingWorking with remote agentsWorking in multiple operation systemsCreating UI using GroovyWriting custom Jelly(?) tagsMaintaining backwards compatibility

Agenda

Documentation:

Writing custom Jelly(?) tags

Writing custom Jelly(?) tags

Simple as 1,2…that’s it.

Writing custom Jelly Groovy tags

1. Implement

1 class MotivationTagLib extends 2 AbstractGroovyViewModule { 3 def tools = .namespace( builder ) 'hudson/tools' 4 5 MotivationTagLib(JellyBuilder b) { 6 (b) super 7 } 8 9 def evilLaugh() { 10 .label(tools ) 'mu-ha-ha!'11 } 12 }

2. Use!

1 import org._10ne.jenkins.MotivationTagLib 2 3 f = namespace( ) 'lib/form' 4 m = new MotivationTagLib(builder); 5 6 f.entry( : title ) { '' 7 m.evilLaugh() 8 f.checkbox(…) 11 }

Vote and guessingWorking with remote agentsWorking in multiple operation systemsCreating UI using GroovyWriting custom Jelly(?) tagsMaintaining backwards compatibility

Agenda

Back to Motivation plugin…

Maintaining backwards compatibility

Rename defaultMotivatingMessageto

motivatingMessage

What happens to existing configuration on users machines?

Refactoring!

Register field (or class) aliasIn Initializer that runs before plugins started

More complex cases might reqiure XStream converter

XStream Aliasing To The Rescue

1 @Initializer(before = PLUGINS_STARTED) 2 public static void addAliases() { 3 Items.XSTREAM2.aliasField("defaultMotivatingMessage", 4 DescriptorImpl.class, "motivatingMessage"); 5 }

See you at our DEMOzone!

Thank you!

Thank You To Our Sponsors

Recommended