Installing Jenkins and RVM

Update: Contrary to the module readme as of 12/1/2016, this WILL result in a VM running Jenkins 2, rather than version 1.

It’s time to get started installing Jenkins for use with Puppet. I’ll be installing this in my home lab on a vm called jenkins (so inventive, I know) as an all-in-one server for simplicity. I don’t know Jenkins real well so I don’t know what I should be doing for masters and build servers, but my home lab needs are small anyway. I gave it 2 cores and 1 GB of RAM, which I think is on the low end for Jenkins. My initial exploration led me to believe I’ll need to install Jenkins as well as RVM, since my base OS image is CentOS 7 (Ruby 2.0.0) and the current Puppet 4 AIO build is against Ruby 2.1. I’m using Puppet and the rtyler/jenkins module to set up Jenkins, since I don’t know anything about its innards. Down below, you’ll see that I installed RVM by hand, after which it occurred to me that there’s a perfectly cromulent maestrodev/rvm module that I’ve used in the past – something I may change out later, but I already wrote my manifest so I’m going to share it anyway!

I used Jenkins jobs to experiment a lot with this setup, such as enabling RVM on a job and seeing what errors showed up in the log, but I’m going to hold off on showing that magic until the next article. I will still explain where portions of the manifest came from, just without the actual errors.

Before we go any further, make sure you have the jenkins module and its dependencies added to your Puppetfile, .fixtures.yml, and wherever else you might need to track it.

First, we need a jenkins role. This is pretty simple – though again, maybe a refactor that separates rvm would require an additional profile::rvm or similar:

class role::jenkins {
  include profile::base  # All roles should have the base profile
  include profile::jenkins
}

Easy, peasy. The profile is a little more detailed, especially with rvm entwined. Let me show it, then explain it:

class profile::jenkins (
  Optional[Array] $plugins = undef,
) {
  include ::jenkins

  ::sudo::conf { 'jenkins':
    priority => 10,
    content  =>
'jenkins ALL=(root) NOPASSWD: /usr/bin/yum
Defaults!/usr/bin/yum !requiretty',
  }

  if $plugins {
    $plugins.each |$plugin| {
      jenkins::plugin { $plugin: }
    }
  }

  # RVM pre-reqs
  ['patch', 'libyaml-devel', 'glibc-headers', 'autoconf', 'gcc-c++', 'glibc-devel', 'readline-devel', 'zlib-devel', 'libffi-devel', 'openssl-devel', 'bzip2', 'automake', 'libtool', 'bison', 'sqlite-devel'].each |$package| {
    package { $package:
      ensure => present,
    }
  }

  firewall { '100 Jenkins HTTP/S inbound':
    dport  => [8080],
    proto  => tcp,
    action => accept,
  }
}

We start with an optional parameter for plugins. I believe you can manage plugins through Jenkins itself if you don’t want puppet to manage it; I’m not sure what happens if you do both.  Next, we include the jenkins module. We’ll set some parameters through hiera in a bit. We have a sudo configuration for the jenkins user so it can run yum installs via sudo without a password or tty. I’m not sure how necessary that actually is, since that was primarily to install RVM which ended up being done elsewhere in the end.

If the $plugins parameter has any values, we iterate through it and install the listed plugins. You can find a list of plugin IDs here, or look for the Plugin ID field on a plugin page. For example, the Puppet Enterprise Pipeline Plugin is called puppet-enterprise-pipeline.

Next up is the list of packages that RVM relies on on CentOS 7 – not only is RVM entwined with Jenkins, but it’s tied to the OS in use. Another reason to migrate to the rvm plugin. Finally we have a firewall rule to allow inbound port tcp/8080. If you plan to hook this up to Github or another external system, make sure you allow this through NAT/firewall policies on your edge, as well.

Here’s the rspec tests for that class, using rspec-puppet-facts:

require 'spec_helper'
describe 'profile::jenkins', :type => :class do
  on_supported_os.each do |os, facts|
    let :facts do
      facts.merge(
        jenkins_plugins: '',
        clientcert: 'jenkins'
      )
    end

    context 'with defaults for all parameters' do
      it { is_expected.to create_class('profile::jenkins') }
      it { is_expected.to contain_class('jenkins') }
      it { is_expected.to contain_sudo__conf('jenkins') }
      it { is_expected.to contain_jenkins__plugin('git') }
      it { is_expected.to contain_jenkins__plugin('puppet-enterprise-pipeline') }
      it { is_expected.to create_firewall('100 Jenkins HTTP/S inbound') }

      %w(patch libyaml-devel glibc-headers autoconf gcc-c++ glibc-devel readline-devel zlib-devel libffi-devel openssl-devel bzip2 automake libtool bison sqlite-devel).each do |package|
        it { is_expected.to contain_package(package) }
      end
    end
  end
end

The clientcert fact loads the following hiera data for testing:

---
profile::jenkins::plugins:
  - git
  - puppet-enterprise-pipeline

Finally, here’s my Jenkins hieradata. I’ve split the plugins into those my research suggested I will need, versus the indirect dependencies of those plugins. I don’t actually know what plugins I will end up using, so the split will allow me to remove individuals later and then test the dependencies. Dependency tracking remains the worst.

---
classes:
  role::jenkins
profile::jenkins::plugins:
  # Plugins directly used
  - git
  - puppet-enterprise-pipeline
  - rvm
  - promoted-builds
  - config-file-provider
  - junit
  - envinject
  - bitbucket
  - bitbucket-build-status-notifier
  - bitbucket-pullrequest-builder
  - github
  # Dependencies
  - workflow-scm-step
  - git-client
  - mailer
  - matrix-project
  - scm-api
  - ssh-credentials
  - plain-credentials
  - workflow-basic-steps
  - workflow-api
  - script-security
  - workflow-cps
  - workflow-step-api
  - structs
  - maven-plugin
  - display-url-api
  - token-macro
  - ruby-runtime
  - javadoc
  - ace-editor
  - workflow-support
  - jquery-detached
  - mercurial
  - workflow-job
  - multiple-scms
  - github-api

One puppet run later, and I can reach Jenkins and see my plugins!

installing-jenkins-with-rvm-fig-1

After some tweaking, I can not only use RVM, but I can have Jenkins perform a new build when I push a change!

installing-jenkins-with-rvm-fig-2

installing-jenkins-with-rvm-fig-3

I’ll share the details on how to set the job up later, after I figure out how I actually did it 🙂