770 likes | 967 Views
Kohsuke Kawaguchi kohsuke.kawaguchi@cloudbees.com CloudBees , Inc. Hudson Dev Workshop. Why a Hudson plugin ?. 300+ people have done it. Can’t be that hard Integrate Hudson with your favorite tools, systems, and etc. Make Hudson speak in domain specific terms
E N D
Kohsuke Kawaguchi kohsuke.kawaguchi@cloudbees.com CloudBees, Inc. Hudson Dev Workshop
Why a Hudson plugin? • 300+ people have done it. Can’t be that hard • Integrate Hudson with your favorite tools, systems, and etc. • Make Hudson speak in domain specific terms • Instead of batch files or shell scripts
Pre-requisite • Maven 2 • Terminal and/or command prompt • IDE of your choice • The following entry in your .m2/settings.xml • Windows user will find this in %USERPROFILE%\.m2\settings.xml <settings> <pluginGroups> <pluginGroup>org.jvnet.hudson.tools</pluginGroup> </pluginGroups> </settings>
Create a skeleton • CUI wizard to create a skeleton • This will download a lot from the internet if this is the first time • Download seed.zip and unzip it in ~/.m2/repository for faster bootstrap • From my USB key $ mvn -cpuhpi:create
Hudson Plugin Community • https://github.com/hudson/ • Developer resources • IRC channel: #hudson • Mailing list: hudson-dev@googlegroups.com • Wiki: http://wiki.hudson-ci.org/display/HUDSON/Extend+Hudson
Build your Hudson plugin • Update Hudson version to 1.387 • In reference to parent POM • Hudson plugin follows a Maven standard • And while you wait for Maven to build… $ mvn package $ ls target/*.hpi
Let’s look at source files • POM • Source files • Jelly files • Help HTML files
Debug a Hudson plugin • Start your plugin in an embedded Hudson • If port 8080 is occupied, use –Dport=12345 • Point your browser to http://localhost:8080/ $ mvnhpi:run
Play with the hello world builder • Configure a new job • Add a “Say hello world” build step • Run the build and see hello world • Where is HUDSON_HOME? • Hint: system configuration
Interactive Development • Edit help HTML file, save • Just reload the browser and see the change • Edit Jelly files, save • Just reload the browser and see the change
Interactive Development • For Eclipse • Edit source code, save • Wait for Hudson to auto reload • For other IDEs • Edit source code, compile • Wait for Hudson to auto reload
Sometimes auto-reload is annoying • Most often useful with “mvnDebug” • So that you can reload classes from debugger • Add –DscanIntervalSeconds=0 to Maven
When your plugin is ready… • “Manage Hudson” > “Manage Plugins” > “Advancced”
Key ingredient check list • @DataBoundConstructor • @Extension • DescriptorImpl • Package structure for Jelly files • Help files by convention
Basic pattern of plugins • Pick an extension point • http://wiki.hudson-ci.org/display/HUDSON/Extension+points • Implement it • Create a descriptor with @Extension • Or in some cases there’s no descriptor and the implementation class itself gets @Extension
Exercise: add a new config field • Add another text field to accept another name, then say hello to both • Add a help file for this new text field
UI Samples • Unfortunately, much of config.jelly editing is monkey-see-monkey-do • UI samples = our attempt to fix this
Exercise: add a new config field • Add auto-completion to the name field • Note the method name has to match with @field • Extra Credits • Try other UI samples and see if you can copy them
Form Validation • Write doCheckXyz method for xyz field • “value” parameter to receive the current value • Can also retrieve nearby sibling values • Return/throw FormValidation instance for status public FormValidationdoCheckPort(@QueryParameter String value) { if(looksOk(value)) return FormValidation.ok(); else return FormValidation.error("There's a problem here"); }
Different datatypes available • URL • int • boolean • Enum • hudson.util.Secret • Persisted and sent to browser in encrypted form • Can be extended by adding converter
Exercise: play with Secret • Add another configuration field whose type is hudson.util.Secret • Inspect the persisted config.xml • ./work/jobs/JOBNAME/config.xml • Inspect the value set in the config page • Trace the execution in the debugger
Playing with Launcher • Abstraction for forking processes • Works even when a build is on a slave • Fluent API • launcher.launch().doThis().doThat().start() • or join()
Playing with BuildListener • Used to write to build console • getLogger() : PrintStream • Used to report error • e.printStackTrace(listener.error(“Failed to do X”));
Exercise: Launcher&BuildListener • Tweak HelloWorldBuilder to fork a process and send its output to build console • E.g., “uname –a” or “cmd.exe /?”
FilePath: working with files • java.io.File on steroid • Represents a file or a directory • Lots of convenient file operations • Works transparently on files on slaves
Exercise: FilePath • Tweak HelloWorldBuilder to play with FilePath • Use AbstractBuild.getWorkspace() • Create several text files and create a zip file from them • Compute md5 checksums of all the files • Hint: getDigest()
Descriptor : Describable • Descriptor : Describable = Class : Object • Describable • Created from UI through Descriptor • Live as long as the configuration remains the same • Descriptor • Singletons • Capture behaviors and operations that are “static” • Global configuration, form validation • … is a static nested type of Describable by convention
@Extension • Enumerated during compile time to generate index • Sometimes “mvn compile” is needed for this • Used at runtime to find extensions
Side track: RootAction • Useful for experimenting with views • Extension point to add a menu item to the top page • Descriptor-less, because it’s not configured from anywhere
Apache Commons Jelly • XML based template engine • Think of it as JSPX+JSTL+customtaglibs • Used to render HTML • And other XMLs, such as RSS, JNLP, … • Most people start by monkey-see-monkey-do • References • http://commons.apache.org/jelly/tags.html • http://hudson-ci.org/maven-site/hudson-core/jelly-taglib-ref.html
Apache Commons JEXL • EL on steroid • ${foo.bar[5]+”abc”} • Allow method calls • XML friendly operators • “and”/”or”,”lt”,”ge” etc in addition to &&, ||, <, >= • a?b:c and a?:b (=a?a:b)
Object-oriented views • Views are like methods • Co-located to classes via naming convention • foo.jelly for org.acme.Bar should be in org/acme/Bar/foo.jelly • Views are inherited to subtypes • In JEXL, “it” variable refers to the “this” object • “index.jelly” plays the special role
Exercise: Jelly, JEXL, and RootAction • Create a new root action “MyRootAction” • See javadoc for what your methods need to return • Add index.jelly • Start from this: • Play with Jelly and JEXL • Add methods to your class and invoke it from view • Use <j:forEach> to say hello 10 times • Otherwise be creative! <j:jelly xmlns:j=“jelly:core”> <html><body><h1> Hello from ${it.getClass()} </h1></body></html> </j:jelly>
Object-oriented Views • In Hudson, URLs are mapped to object graph • And this determines how request is eventually handled • IOW, controller layer is implicit • More precisely, this is in Stapler • See HTTP headers for evaluation trace • So if you add “bar.jelly” to MyRootAction,
Exercise: Object-oriented views • Add “bar.jelly” to MyRootAction • Modify index.jelly to add a hyperlink to the new page • RefactorMyRootAction and introduce a base type • Push down bar.jelly to the new base class. See how the UI behaves
URL → Object graph mapping • Typical traversal methods • getFoo() → …/foo/ • getFoo(“bar”) → …/foo/bar/ • getDynamic(“foo”) → …/foo/… • Exact rules are in https://stapler.dev.java.net/reference.html
Exercise: URL → Object graph • Define immutable Employee class • And populate several of them from MyRootAction • Define index.jelly on Employee and create navigable UI public class Employee { public int number; public String name; public Employee boss; public List<Employee> reports; … } public class MyRootAction { List<Employee> employees; }
Programmatic request handling • Instead of “foo.jelly”, define “doFoo” method • Parameters are injected • StaplerRequest (<: HttpServletRequest) • StaplerResponse (<: HttpServletResponse) • @QueryParameter • @AncestorInPath • Return value / exception becomes HTTP response (or void) • Or void and control it yourself • org.kohsuke.stapler.HttpResponse
Demo • Employee.doTerminate()
Exercise: doXyz method • Define a form that takes one text field and update the employee name
XStream • This is how Hudson persists most data • Characteristics • Clean almost human-readable XML • No mapping required / semi-transparent to code • Supports arbitrary object graph • Customizable enough • Robust in the face of partial unmarshalling failure
XStream: naïve example public class Employee { public int number; public String name; } <org.acme.Employee> <number>1</number> <name>Kohskue</name> </org.acme.Employee>
Persistence and extension points • If/how your extensions are persisted is described in javadoc • E.g., Builder is persisted as a part of AbstractProject • SecurityRealm is persisted as a part of Hudson • etc.
XmlFile • Used if you need to persist things on your own • Represents a XML file that contains persisted objects • Important methods • write(Object) • Object read() • void unmarshal(Object)