Multistage deployments for Hugo using Jenkins Pipelines

tl;dr: If you use a static site generator like hugo as a replacement for a cms, you will sooner or later reach the point where automatic deployments with Jenkins become useful. Using Jenkins pipelines, multi-stage deployments based on different git branches are no problem.

Contents

A friend of mine introduced me to Jenkins Pipelines some month ago. So far I haven’t found the time to change all my Jenkins jobs, so now I’m doing it as part of the migration to Hugo.

Build Process

I use gulp1 and npm2/webpack3 for my blog’s buildchain. Depending on how the process looks like, you may have to adapt the individual commands, but the basic idea remains the same.

Two tasks are registered in package.json which then call gulp:

"scripts": {
  "build": "./node_modules/.bin/gulp build",
  "staging": "./node_modules/.bin/gulp staging"
}
  • staging: this tasks builds the complete blog and includes all draft / future posts
  • build: this build the blog for my prod system

The prod system runs on the master branch, staging uses development. We will use the branch-name to decide, which task to run and where the results shoudl be deployed.

Jenkinsfile

The basic idea is to use the branchname in the Jenkinsfile to set different variables. In this case they are:

  • targetedEnv: build or staging, decides which task to run
  • rsyncTarget: foldername for deployments (e.g. www.foo.bar and staging.foo.bar)

Afterwards Jenkins can checkout the SCM (including submodules), install Node.js (using the nvm-wrapper-plugin4), run the requried commands and then rsync all content to the configured remote host.

def getEnvFromBranch(branch) {
  if (branch == "master") {
    return "build"
  } else {
    return "staging"
  }
}
def getRsyncTargetFromBranch(branch) {
  if (branch == "master") {
    return "YOUR-PROD-FOLDER"
  } else {
    return "YOUR-STAGING-FOLDER"
  }
}

pipeline {
  agent any

  options {
    timeout(time: 15, unit: "MINUTES")
    buildDiscarder(
        logRotator(
            artifactDaysToKeepStr: "",
            artifactNumToKeepStr: "",
            daysToKeepStr: "10",
            numToKeepStr: "15")
    )
  }

  triggers {
    pollSCM("H/5 * * * *")
  }
  environment {
    targetedEnv = getEnvFromBranch(env.BRANCH_NAME)
    rsyncTarget = getRsyncTargetFromBranch(env.BRANCH_NAME)
  }
  stages {
    stage("prepare") {
      steps {
        checkout(
            [$class: "GitSCM",
             branches: scm.branches,
             doGenerateSubmoduleConfigurations: false,
             extensions: [
                 [$class: "SubmoduleOption",
                  disableSubmodules: false,
                  parentCredentials: true,
                  recursiveSubmodules: true,
                  reference: "",
                  trackingSubmodules: true]
             ],
             userRemoteConfigs: [
                 [credentialsId: "YOUR-GIT-CREDENTIALS-ID",
                 url: "YOUR_GIT_URL"]
             ]
            ]
        )
      }
    }
    stage("build") {
      steps {
        nvm(nvmInstallURL: "https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh",
            nvmIoJsOrgMirror: "https://iojs.org/dist",
            nvmNodeJsOrgMirror: "https://nodejs.org/dist",
            version: "lts/*") {
          sh "npm install"
          sh "npm run ${targetedEnv}"
        }
      }
    }
    stage("deploy") {
      steps {
        sshagent(["YOUR_SSH_CREDENTIALS"]) {
          sh "rsync -a --delete  '$WORKSPACE/public/' foo@bar:/var/www/${rsyncTarget}/"
        }
      }
    }
  }
}

Footnotes

Tags

Comments

Related