{
    "componentChunkName": "component---src-templates-blog-post-tsx",
    "path": "/blog/2012-01-16-implementing-a-jenkins-extension-point-with-native-api-in-ruby/",
    "result": {"data":{"blogPost":{"title":"Implementing a Jenkins Extension Point with the Native Java API inside a Ruby Plugin","slug":"/blog/2012-01-16-implementing-a-jenkins-extension-point-with-native-api-in-ruby/","authorNodes":[{"name":"Charles Lowell","slug":"/people/charles-lowell/"}],"markdown":{"html":"<blockquote>\n<p>In which I elaborate why the idomatic Ruby API is sometimes not enough,\nand describe a method to harness the full power of the underlying\nJenkins API while still happily coding your extension in Ruby</p>\n</blockquote>\n<h2 id=\"the-ruby-api\" style=\"position:relative;\"><a href=\"#the-ruby-api\" aria-label=\"the ruby api permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The Ruby API</h2>\n<p>One of the primary goals of the <a href=\"http://blog.thefrontside.net/2011/05/12/what-it-take-to-bring-ruby-to-jenkins\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">effort to bring Ruby to Jenkins</a>\nis to enable developers to extend Jenkins in a way that looks and\nfeels like a normal Ruby environment. This means using rake, bundler\nERB, and plain old Ruby objects to roll your plugin.</p>\n<p>For example, the <a href=\"https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/tasks/BuildStep.java\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">native BuildStep</a> class is made available through\nthe <a href=\"https://github.com/jenkinsci/jenkins-plugin-runtime.rb/blob/master/lib/jenkins/tasks/build_step.rb\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Ruby BuildStep</a>. They are similar to be sure, but the Ruby\nobject is much less complicated, and actually bears no relation to its\nJava equivalent inside the Java hiearchy of inheritance.</p>\n<p>Exposing this simplicity is a worthy goal, but to do so requires\ncareful consideration of each Jenkins Java API and how to provide its\nfunctionality in the Ruby way --no mean feat given the breadth of the\nJenkins.</p>\n<p>Sadly, it means that these Ruby-friendly APIs must necessarily lag\nbehind their Java counterparts.</p>\n<p>This can be a serious source of frustration for those looking to\ndive into Jenkins Ruby development right now. Initial excitement is\nquickly dulled when you find out that the extension point that you\nwanted to implement is unavailable from Ruby land. You might get\ndiscouraged and feel like you might as well be coding your plugin\nwith Java.</p>\n<p>Well don't lose heart! I'd like to share with you a super easy way to\nwrite your extension points in Ruby even when there isn't a friendly\nwrapper. We'll actually implement a very handy extension point called\n<code class=\"language-text\">RunListener</code> within a Ruby plugin even though it is not part of the\nofficial Ruby API. We'll do it by scripting the Java class directly\nwith JRuby's Java integration feature, and then register it directly\nwith the plugin runtime.</p>\n<h2 id=\"the-extension-point\" style=\"position:relative;\"><a href=\"#the-extension-point\" aria-label=\"the extension point permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The Extension Point</h2>\n<p>We'll be working with the Jenkins <a href=\"https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/listeners/RunListener.java\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">RunListener</a> interface. This is\na wonderful extension point that allows you to receive callbacks\nat each point during the actual running of a build. There's currently\nno nice ruby API for it, but we won't let that stop us.</p>\n<p>First, let's create a new plugin called <em>my-listener</em>. We'll do this\nwith the <code class=\"language-text\">jpi new</code> command.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">legolas:Jenkins cowboyd$ jpi new my-listener\n    create  my-listener/Gemfile\n    create  my-listener/my-listener.pluginspec</code></pre></div>\n<blockquote>\n<p>Fun fact: 'jpi' is an acronym for (J)enkins (P)lug-(I)n. You can\ninstall the tool with rubygems: <code class=\"language-text\">gem install jpi</code></p>\n</blockquote>\n<p>Next, we'll cd into our new plugin and create our listener class\ninside the models/ directory. Jenkins will automatically evaluate\neverything in this directory on plugin initialization.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">legolas:Jenkins cowboyd$ cd my-listener/\nlegolas:my-listener cowboyd$ mkdir models\nlegolas:my-listener cowboyd$ touch models/my_listener.rb</code></pre></div>\n<p>Our ultimate goal here is to implement a <code class=\"language-text\">RunListener</code>, so let's\ngo ahead and start our class definition inside that file.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">class MyListener &lt; Java.hudson.model.listeners.RunListener\n  def initialize()\n    super(Java.hudson.model.Run.java_class)\n  end\nend</code></pre></div>\n<p>There's a couple key takeaways here. First, notice that we use\nJRuby integration to extend the class\n<code class=\"language-text\">hudson.model.listeners.RunListener</code> directly. Second, and this is\na gotcha anytime you extend a Java class: you <em>must</em> invoke one of\nthe Java super constructors if it does not have a default\nconstructor. I can't tell you how many times I've been bitten by this.</p>\n<p>In our case, the <code class=\"language-text\">RunListener</code> class filters which jobs\nit will provide callbacks for by class. By providing a more specific\nclass to the constructor, you limit the scope of jobs you'll receive\nto subclasses of that class. For our purposes, we cast a pretty wide\nnet by selecting all builds via the <code class=\"language-text\">AbstractBuild</code> Java class.</p>\n<blockquote>\n<p>Pro Tip: when you're implementing a native Java API, it really\nhelps to have the javadoc open in one window so that you\ncan view the documentation and crib from the source</p>\n</blockquote>\n<p>Now that we've got our class defined, let's implement some methods!\nWe'll add callbacks for when a build is started and when it's\ncompleted.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">class MyListener &lt; Java.hudson.model.listeners.RunListener\n  def initialize()\n    super(Java.hudson.model.AbstractBuild.java_class)\n  end\n  def onStarted(run, listener)\n    listener.getLogger().println(\"onStarted(#{run})\")\n  end\n  def onCompleted(run, listener)\n    listener.getLogger().println(\"onCompleted(#{run})\")\n  end\nend\nJenkins.plugin.register_extension MyListener.new</code></pre></div>\n<p>And finally, on the last line, we actually inform Jenkins about the\nexistence of our new Listener with the call to\n<code class=\"language-text\">Jenkins.plugin.register_extension</code></p>\n<p>And that's about it. We can start up our test server with our <code class=\"language-text\">jpi</code>\ntool to see our listener in action.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">legolas:my-listener cowboyd$ jpi server\nListening for transport dt_socket at address: 8000\nRunning from: /Users/cowboyd/.rvm/gems/jruby-1.6.5/gems/jenkins-war-1.446/lib/jenkins/jenkins.war\n...\nJan 16, 2012 12:46:15 AM ruby.RubyRuntimePlugin start\nINFO: Injecting JRuby into XStream\nLoading /Users/cowboyd/Projects/Jenkins/my-listener/models/my_listener.rb\nINFO: Prepared all plugins\n...\nINFO: Jenkins is fully up and running</code></pre></div>\n<p>To view the output, create a freestyle build called HelloWorld that\ndoesn't have any build steps at all, build it and view the console\noutput. You should see something like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">Started by user anonymous\nonStarted(#&lt;Java::HudsonModel::FreeStyleBuild:0x2870068a>)\nonCompleted(#&lt;Java::HudsonModel::FreeStyleBuild:0x2870068a>)\nFinished: SUCCESS</code></pre></div>\n<h2 id=\"the-sweet-reality\" style=\"position:relative;\"><a href=\"#the-sweet-reality\" aria-label=\"the sweet reality permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The Sweet Reality</h2>\n<p>Even though we were dealing more directly with Java, we were\nstill able to use all of the simplicity that comes with developing\nplugins in Ruby. Furthermore, even though our extension point was just\nscripted Java, it doesn't mean that inside its methods it can't call\nas much pure Ruby code as its heart desires.</p>\n<p>I hope that if you're considering writing your next (or your first!)\nJenkins plugin in Ruby, you'll feel confident that you can always\nfall back to the native APIs at any point.</p>","frontmatter":{"date":"January 16, 2012","description":null,"tags":["jenkins","ruby","java"],"img":{"childImageSharp":{"fixed":{"src":"/static/bf4163a654c532fd8669027037560ed5/31987/2012-01-16-implementing-a-jenkins-extension-point-with-native-api-in-ruby-1.png"}}}}}}},"pageContext":{"id":"b1fc7ba8-ccaf-5de7-aa8a-b9f02cd0d71a"}},
    "staticQueryHashes": ["1241260443"]}