starting-up-a-jenkins-clone-safely

Why bother starting Jenkins in neutral?

Jenkins can be a dangerous virtual machine to bring up.  This situation arises when I’m recovering one, cloning one, or testing provisioning automation.  The damage one can cause is hard to predict because it depends on exactly what yours does, but I’ll try to paint a couple common pictures for you.

If your Jenkins is purely Integration, it will wake up and start pulling down code, build it, and [the dangerous part] start publishing artifacts to Nexus.  These versions will pollute the your repository and the history ( auditing / non-repudiation and all ) is now on the wrong Jenkins!

If your super-powered Jenkins is Delivery also then unintended versions could start making through testing environments or higher. Independent of the danger, you might just want to control starting up in neutral so that it’s working.

How to start Jenkins in a “shutdown mode”

When Jenkins is in shutdown mode it won’t start any new jobs.  So, how do we get Jenkins in to shutdown mode before it has a chance to start a bunch of jobs? The secret is that Jenkins will run any groovy script on start up if it is located in the correct place.  Even more perfect, would be the exact script we need.  Specifically, we’ll write the following script to $JENKINS_HOME/init.groovy

import jenkins.model.*;
Jenkins.instance.doQuietDown();

Jenkins starts up safely, but I can’t run a single job with out starting it up (and if I do that all the polling jobs queued will start).

Disabling polling

So how do we disable polling? For this I turned to brute force. Or rather, my version of it. Every job contains a config.xml that has its polling settings at the following xpath: ./triggers/hudson.triggers.SCMTrigger/spec

If we loop through every config.xml and comment out (“#”) the cron polling lines, they all polling will effectively be disabled. We comment it out so we can easily inverse the process because we haven’t thrown the info away. The Ruby cli is below:

#!/usr/bin/env ruby
require 'optparse'

opts = { :comment=>true,
:xpath=>"./triggers/hudson.triggers.SCMTrigger/spec",
:dir=>nil,
:fileglob=>"config.xml"
}

OptionParser.new do |o|
  o.on( "-u", "--uncomment", "Uncomments the code instead") do |u|
  opts[:comment] = (not u)
end

o.on( "-x","--xpath path", "Use Alternate Xpath, Default:#{opts[:xpath]}") do |x|
  opts[:xpath] = x
end
 
o.on( "-d","--directory dir", "Search directory for config.xml instead of cli file list") do |d|
  opts[:dir] = d
end

o.on( "-f","--file glob", "For use with -d, override config.xml search glob") do |f|
  opts[:fileglob]= f
end

end.parse!
 

if opts[:dir]
  Dir[ "#{opts[:dir]}/**/#{opts[:fileglob]}" ].each {|filepath|
    puts "Found #{filepath}"
    ARGV.push filepath
  }
end

require 'rexml/document'
ARGV.each { |filename|
  print "Changing comments on #{filename}"
  begin
    doc = REXML::Document.new(File.open(filename));
    doc.context[:attribute_quote] = :quote # use jenkins default quotes(") reduces the diff
    current = doc.root.get_elements( opts[:xpath]).first.text
    replacement = ( opts[:comment] ? current.gsub(/^(.*)$/,'#\1') : current.gsub(/^#(.*)$/,'\1') )
    doc.root.get_elements( opts[:xpath]).first.text=replacement
    File.write( filename, doc )
    puts " complete"
  rescue
    puts " failed"
  end
}

Until the chameleon no longer needs to hide,
Jonathan Malachowski

Leave a comment

Your email address will not be published. Required fields are marked *

X