A Declarative Jenkinsfile That Doesn't Fight You

Scripted Jenkinsfiles turn into unreadable Groovy fast. Declarative syntax with a few escape hatches covers almost everything.

The pipeline

pipeline {
  agent { label 'docker' }

  environment {
    IMAGE = "registry.internal/checkout"
  }

  stages {
    stage('Test') {
      parallel {
        stage('Unit') {
          steps { sh 'make test-unit' }
        }
        stage('Lint') {
          steps { sh 'make lint' }
        }
      }
    }

    stage('Build') {
      steps {
        sh "docker build -t ${IMAGE}:${GIT_COMMIT} ."
      }
    }

    stage('Push') {
      steps {
        withCredentials([usernamePassword(
          credentialsId: 'registry-creds',
          usernameVariable: 'REG_USER',
          passwordVariable: 'REG_PASS'
        )]) {
          sh "echo $REG_PASS | docker login registry.internal -u $REG_USER --password-stdin"
          sh "docker push ${IMAGE}:${GIT_COMMIT}"
        }
      }
    }

    stage('Deploy') {
      when { branch 'main' }
      steps {
        sh "kubectl set image deployment/checkout checkout=${IMAGE}:${GIT_COMMIT}"
      }
    }
  }

  post {
    failure {
      slackSend(channel: '#deploys', message: "Build failed: ${env.BUILD_URL}")
    }
  }
}

Useful CLI checks against Jenkins itself

Trigger a build from the CLI:

curl -X POST -u user:token "https://jenkins.internal/job/checkout/build"

Tail the console log of the last build:

curl -s -u user:token "https://jenkins.internal/job/checkout/lastBuild/consoleText"

when { branch 'main' } on the deploy stage is what keeps every feature branch from also deploying to prod.