2015-07-20 20:27:26 +00:00
|
|
|
|
---
|
|
|
|
|
layout: post
|
|
|
|
|
title: Automating Sculpin Builds with Jenkins CI
|
2015-07-21 12:16:17 +00:00
|
|
|
|
date: 2015-07-21
|
2015-07-20 20:27:26 +00:00
|
|
|
|
tags:
|
|
|
|
|
- sculpin
|
|
|
|
|
- jenkins
|
|
|
|
|
slug: automating-sculpin-jenkins
|
|
|
|
|
---
|
2015-09-02 21:02:46 +00:00
|
|
|
|
As part of re-building this site with [Sculpin](http://sculpin.io), I wanted to automate the deployments, as in I
|
|
|
|
|
wouldn't need to run a script like
|
|
|
|
|
[publish.sh](https://raw.githubusercontent.com/sculpin/sculpin-blog-skeleton/master/publish.sh) locally and have that
|
|
|
|
|
deploy my code onto my server. Not only did that mean that my local workflow was simpler (update, commit and push,
|
|
|
|
|
rather than update, commit, push and deploy), but if I wanted to make a quick edit or hotfix, I could log into GitHub or
|
|
|
|
|
Bitbucket (wherever I decided to host the source code) from any computer or my phone, make the change and have it
|
|
|
|
|
deployed for me.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
I'd started using [Jenkins CI](http://jenkins-ci.org) during my time at the Drupal Association, and had since built my
|
|
|
|
|
own Jenkins server to handle deployments of Drupal websites, so that was the logical choice to use.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
|
|
|
|
## Installing Jenkins and Sculpin
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
If you don’t already have Jenkins installed and configured, I'd suggest using [Jeff Geerling](http://jeffgeerling.com/)
|
|
|
|
|
(aka geerlingguy)'s [Ansible role for Jenkins CI](https://galaxy.ansible.com/list#/roles/440).
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
I've also released an [Ansible role for Sculpin](https://galaxy.ansible.com/list#/roles/4063) that installs the
|
|
|
|
|
executable so that the Jenkins server can run Sculpin commands.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
|
|
|
|
## Triggering a Build from a Git Commit
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
I created a new Jenkins item for this task, and restricted where it could be run to `master` (i.e. the Jenkins server
|
|
|
|
|
rather than any of the nodes).
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
|
|
|
|
### Polling from Git
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
I entered the url to the [GitHub repo](https://github.com/opdavies/oliverdavies.uk) into the **Source Code Management**
|
|
|
|
|
section (the Git option *may* have been added by the [Git plugin](https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin) that I have installed).
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
As we don’t need any write access back to the repo, using the HTTP URL rather than the SSH one was fine, and I didn’t
|
|
|
|
|
need to provide any additional credentials.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
Also, as I knew that I’d be working a lot with feature branches, I entered `*/master` as the only branch to build. This
|
|
|
|
|
meant that pushing changes or making edits on any other branches would not trigger a build.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
|
|
|
|
![Defining the Git repository in Jenkins](/assets/images/blog/oliverdavies-uk-jenkins-git-repo.png)
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
I also checked the **Poll SCM** option so that Jenkins would be routinely checking for updated code. This essentially
|
|
|
|
|
uses the same syntax as cron, specifying minutes, hours etc. I entered `* * * * *` so that Jenkins would poll each
|
|
|
|
|
minute, knowing that I could make this less frequent if needed.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
|
|
|
|
This now that Jenkins would be checking for any updates to the repo each minute, and could execute tasks if needed.
|
|
|
|
|
|
|
|
|
|
### Building and Deploying
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
Within the **Builds** section of the item, I added an *Execute Shell* step, where I could enter a command to execute.
|
|
|
|
|
Here, I pasted a modified version of the original publish.sh script.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
2015-07-21 20:30:14 +00:00
|
|
|
|
set -uex
|
|
|
|
|
|
2015-07-21 12:15:35 +00:00
|
|
|
|
sculpin generate --env=prod --quiet
|
|
|
|
|
if [ $? -ne 0 ]; then echo "Could not generate the site"; exit 1; fi
|
|
|
|
|
|
|
|
|
|
rsync -avze 'ssh' --delete output_prod/ prodwww2:/var/www/html/oliverdavies.uk/htdocs
|
|
|
|
|
if [ $? -ne 0 ]; then echo "Could not publish the site"; exit 1; fi
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
This essentially is the same as the original file, in that Sculpin generates the site, and uses rsync to deploy it
|
|
|
|
|
somewhere else. In my case, `prodwww2` is a Jenkins node (this alias is configured in `/var/lib/jenkins/.ssh/config`),
|
|
|
|
|
and `/var/www/html/oliverdavies.uk/htdocs` is the directory from where my site is served.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
|
|
|
|
## Building Periodically
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
There is some dynamic content on my site, specifically on the Talks page. Each talk has a date assigned to it, and
|
|
|
|
|
within the Twig template, the talk is positoned within upcoming or previous talks based on whether this date is less or
|
|
|
|
|
greater than the time of the build.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
|
|
|
|
The YAML front matter:
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
...
|
|
|
|
|
talks:
|
|
|
|
|
- title: Test Drive Twig with Sculpin
|
|
|
|
|
date: 2015-07-24
|
|
|
|
|
location: DrupalCamp North
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
The Twig layout:
|
|
|
|
|
|
|
|
|
|
{% raw -%}
|
|
|
|
|
{% for talk in talks|reverse if talk.date >= now %}
|
|
|
|
|
{# Upcoming talks #}
|
|
|
|
|
{% endfor %}
|
|
|
|
|
|
|
|
|
|
{% for talk in talks if talk.date < now %}
|
|
|
|
|
{# Previous talks #}
|
|
|
|
|
{% endfor%}
|
|
|
|
|
{%- endraw %}
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
I also didn’t want to have to push an empty commit or manually trigger a job in Jenkins after doing a talk in order for
|
|
|
|
|
it to be positioned in the correct place on the page, so I also wanted Jenkins to schedule a regular build regardless of
|
|
|
|
|
whether or not code had been pushed, so ensure that my talks page would be up to date.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
After originally thinking that I'd have to split the build steps into a separate item and trigger that from a scheduled
|
|
|
|
|
item, and amend my git commit item accordingly, I found a **Build periodically** option that I could use within the same
|
|
|
|
|
item, leaving it intact and not having to make amends.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
I set this to `@daily` (the same `H H * * *` - `H` is a Jenkins thing), so that the build would be triggered
|
|
|
|
|
automatically each day without a commit, and deploy any updates to the site.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
|
|
|
|
![Setting Jenkins to periodically build a new version of the site.](/assets/images/blog/oliverdavies-uk-jenkins-git-timer.png)
|
|
|
|
|
|
|
|
|
|
## Next Steps
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
This workflow works great for one site, but as I roll out more Sculpin sites, I'd like to reduce duplication. I see this
|
|
|
|
|
mainly as I’ll end up creating a separate `sculpin_build` item that’s decoupled from the site that it’s building, and
|
|
|
|
|
instead passing variables such as environment, server name and docroot path as parameters in a parameterized build.
|
2015-07-21 12:15:35 +00:00
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
I'll probably also take the raw shell script out of Jenkins and save it in a text file that's stored locally on the
|
|
|
|
|
server, and execute that via Jenkins. This means that I’d be able to store this file in a separate Git repository with
|
|
|
|
|
my other Jenkins scripts and get the standard advantages of using version control.
|
2015-08-07 07:08:20 +00:00
|
|
|
|
|
|
|
|
|
## Update
|
|
|
|
|
|
|
|
|
|
Since publishing this post, I've added some more items to the original build script.
|
|
|
|
|
|
2015-08-08 17:41:19 +00:00
|
|
|
|
### Updating Composer
|
|
|
|
|
|
|
|
|
|
if [ -f composer.json ]; then
|
2015-08-08 17:46:16 +00:00
|
|
|
|
/usr/local/bin/composer update
|
2015-08-08 17:41:19 +00:00
|
|
|
|
fi
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
Updates project dependencies via [Composer](https://getcomposer.org/doc/00-intro.md#introduction) if composer.json
|
|
|
|
|
exists.
|
2015-08-08 17:41:19 +00:00
|
|
|
|
|
2015-08-07 07:08:20 +00:00
|
|
|
|
### Updating Sculpin Dependencies
|
|
|
|
|
|
|
|
|
|
if [ -f sculpin.json ]; then
|
|
|
|
|
sculpin update
|
|
|
|
|
fi
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
Runs `sculpin update` on each build if the sculpin.json file exists, to ensure that the required custom bundles and
|
|
|
|
|
dependencies are installed.
|
2015-08-07 07:08:20 +00:00
|
|
|
|
|
|
|
|
|
### Managing Redirects
|
|
|
|
|
|
|
|
|
|
if [ -f scripts/redirects.php ]; then
|
|
|
|
|
/usr/bin/php scripts/redirects.php
|
|
|
|
|
fi
|
|
|
|
|
|
2015-09-02 21:02:46 +00:00
|
|
|
|
I've been working on a `redirects.php` script that generates redirects from a .csv file, after seeing similar things in
|
|
|
|
|
the [Pantheon Documentation](https://github.com/pantheon-systems/documentation) and
|
|
|
|
|
[That Podcast](https://github.com/thatpodcast/thatpodcast.io) repositories. This checks if that file exists, and if so,
|
|
|
|
|
runs it and generates the source file containing each redirect.
|