Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils

def dockerImage = "<%= image %>"
def standardEnvironments = ['staging', 'preproduction', 'production'];
def defaultConfig = [lint: true, test: true, build: true, deploy: false, chart: '<%= chart %>', charts_repository: 'https://charts.internal.doodle-test.com']

node ('jenkins') {
  properties([
    parameters([
      booleanParam(name: 'lint', defaultValue: false, description: 'Execute linting stage'),
      booleanParam(name: 'test', defaultValue: false, description: 'Execute test stage'),
      booleanParam(name: 'build', defaultValue: false, description: 'Execute build stage'),
      booleanParam(name: 'deploy', defaultValue: false, description: 'Execute deploy stage'),
      [$class: 'GitParameterDefinition', name: 'deploy_tag', defaultValue: '', selectedValue: 'NONE', sortMode: 'DESCENDING_SMART', description: 'Which GitHub Release Tag to deploy', type: 'PT_TAG', quickFilterEnabled: true],
      choiceParam(name: 'target', choices: (['choose:', 'sandbox', 'custom namespace'] + standardEnvironments).join('\n'), defaultValue: 'choose:', description: 'Which environment to deploy to'),
      stringParam(name: 'host', defaultValue: '', description: '$host.kubernetes.doodle-test.com'),
      stringParam(name: 'custom_namespace', defaultValue: '', description: 'Custom target namespace name\n💡 Only required when target "custom namespace" is selected'),
      stringParam(name: 'custom_values_override', defaultValue: '', description: 'Custom chart values override filename\n💡 Only required when target "custom namespace" is selected'),
      stringParam(name: 'chart', defaultValue: defaultConfig.chart, description: 'Helm Chart name'),
      stringParam(name: 'charts_repository', defaultValue: defaultConfig.charts_repository, description: 'Helm Charts repository URL'),
      booleanParam(name: 'confirm', defaultValue: false, description: 'Confirm manual run\n🚨 Please review you settings carefully!')
    ])
  ])

  stage('checkout') {
    config = defaultConfig + [branch: env.BRANCH_NAME, tag: env.TAG_NAME, force: true]

    config.hash = checkout(scm).GIT_COMMIT
    config.shortHash = sh(returnStdout: true, script: "git rev-parse --short ${config.hash}").trim()

    if (params.confirm) { // manually parametrized job
      config << params
      config << [
        deploy_tag: params.deploy_tag ? params.deploy_tag : config.shortHash,
        target: config.target == 'custom namespace' ? params.custom_namespace : config.target
      ]

    } else if (config.tag) { // automatic run for git tag 
      config.tag = normalize(text: config.tag, dot: false, cut: false)
      config << [lint: false, test: false, build: false]
      config << [deploy: true, deploy_tag: config.tag, target: 'preproduction']

    } else if (config.branch == 'master') { // automatic run for master
      config.tag = normalize(text: 'latest', dot: false, cut: false)
      config << [deploy: true, deploy_tag: config.shortHash, target: 'staging']

    } else if (config.branch.toLowerCase().startsWith('release')) { // automatic run for release branches
      config << [deploy: true, deploy_tag: config.shortHash, target: 'preproduction']
    }

    unstable = false
    env.GIT_COMMIT = config.hash

    echo """
    =====================================================                                                
    lint       | ${!config.lint ? '❌' : '✅' }
    test       | ${!config.test ? '❌' : '✅' }
    build      | ${!config.build ? '❌' : '✅' }
    tag        | ${!config.tag ? '❌' : config.tag}
    deploy     | ${!config.deploy ? '❌' : '✅'}
    -----------------------------------------------------
    target     : ${!config.deploy ? '' : config.target}
    deploy_tag : ${!config.deploy ? '' : config.deploy_tag}
    hash       : ${config.hash}
    =====================================================
    """
  }

  stage ('prepare') {
    if (!(config.lint || config.test)) return skipStage()

    sh 'yarn install'
  }
  parallel (
    lint: {
      stage ('lint') {
        if (!config.lint) return skipStage()
 
        try {
          sh 'yarn run lint -f checkstyle -o checkstyle.xml'
        } catch (e) {
          currentBuild.result = 'UNSTABLE'
        }
      }
    },
    test: {
      stage ('test') {
        if (!config.test) return skipStage()

        sh 'yarn run test --ci'
      }
    },
    build: {
      stage ('build') {
        if (!config.build) return skipStage()
        
        dockerLogin()

        sh "docker build -t $dockerImage:${config.shortHash} ."
        sh "docker push $dockerImage:${config.shortHash}"
      }
    },
  )

  stage ('reports') {
    if (!(config.lint || config.test)) return skipStage()

    if (config.test) {
      junit 'junit.xml'
    }
    if (config.lint) {
      step([$class: 'CheckStylePublisher', pattern: 'checkstyle.xml'])
    }
  }

  stage ('tag') {
    if (!config.tag) return skipStage()

    dockerLogin()

    sh "docker pull $dockerImage:${config.shortHash}"
    sh "docker tag $dockerImage:${config.shortHash} $dockerImage:${config.tag}"
    sh "docker push $dockerImage:${config.tag}"
  }

  stage ('deploy') {
    if (!config.deploy) return skipStage();

    def escQuot = '\\'.multiply(2) + '"'
    
    helmOptions = [
      namespace: config.target,
      release: config.chart, 
      valuesFile: '', 
      setOverrides: [
        "frontend.image.tag=${escQuot + config.deploy_tag + escQuot}",
        "gitBranch=${config.branch}"
      ], 
      chartsRepository: config.charts_repository
    ]

    if (config.force) {
      uuid = sh(returnStdout: true, script: "cat /proc/sys/kernel/random/uuid | xargs echo -n").trim()
      helmOptions.setOverrides += ["frontend.redeployUUID=${uuid}"]
    }

    if (standardEnvironments.contains(config.target)) {
      helmOptions.valuesFile = "${config.target}.yaml"

    } else if (config.target == 'custom namespace') {
      helmOptions << [valuesFile: config.custom_values_override, namespace: config.custom_namespace]
      helmOptions.setOverrides << "frontend.ingress.host=${config.custom_namespace}.kubernetes.doodle-test.com"

    } else if (config.target == 'sandbox') {
      helmOptions << [release: "${config.chart}-${config.host}"]
      helmOptions.setOverrides << "frontend.ingress.host=${config.host}.kubernetes.doodle-test.com"
      helmOptions.setOverrides << "frontend.sandboxHost=${config.host}"
    }

    kubernetesSetup(config)
    helmUpgrade(config + helmOptions)
  }
}


def normalize(Map args) {
  def options = [text: '', slash: true, dot: true, cut: true, lower: true] + args
  
  result = "${options.text}"
  
  if (options.slash) result = result.replaceAll('/', '-')
  if (options.dot) result = result.replaceAll('\\.', '-')
  if (options.cut) result = sh (script: "echo \"${result}\" | cut -d '-' -f1,2,3", returnStdout: true).trim()
  if (options.lower) result = result.toLowerCase()
  
  return result
}

def skipStage() {
  return Utils.markStageSkippedForConditional(STAGE_NAME)
}

def dockerLogin() {
  withCredentials([[
    $class: 'UsernamePasswordMultiBinding',
    credentialsId: 'nexus',
    usernameVariable: 'NEXUS_USER',
    passwordVariable: 'NEXUS_PASSWORD'
  ]]) {
    sh "docker login nexus.doodle.com:5000 -u ${env.NEXUS_USER} -p ${env.NEXUS_PASSWORD}"
  }
}

def kubernetesSetup(Map args = [:]) {
  def options = [
    target: '', // required
    namespace: args.target, // required
    server: 'https://api.kubernetes.doodle-test.com',
    credentialsIdKToken: 'doodle-jenkins-stg-token',
    credentialsIdCa: 'kubernetes-stg-ca.crt'
  ] + args;

  if(!options.target || !options.namespace) throw new IllegalArgumentException('missing options')

  if (options.target == 'production') { 
    options << [
      server: 'https://api.prod.doodle.com', 
      credentialsIdKToken: 'doodle-jenkins-prod-token', 
      credentialsIdCa: 'kubernetes-prd-ca.crt'
    ] 
  }
  withCredentials([
    string(credentialsId: options.credentialsIdKToken, variable: 'KTOKEN'),
    file(credentialsId: options.credentialsIdCa, variable: 'CA')
  ]) {
    sh """
    kubectl config set-credentials jenkins --token=${env.KTOKEN} 
    kubectl config set-cluster ${options.target}-cluster \
        --server=${options.server} \
        --certificate-authority=${env.CA} \
        --embed-certs=true
    kubectl config set-context ${options.target}-context \
        --cluster=${options.target}-cluster \
        --user=jenkins \
        --namespace=${options.namespace}
    kubectl config use-context ${options.target}-context
    """
  }
}

def helmUpgrade(Map args = [:]) {
  def options = [
    release: '', // required
    namespace: '', // required
    tillerNamespace: args.namespace, // required
    chart: '', // required
    valuesFile: '', // optional
    setOverrides: [], // optional
    chartsRepository: 'https://charts.internal.doodle-test.com',
    chartsDirectory: "${pwd tmp: true}/charts"
  ] + args;
  
  if(!options.release || !options.namespace || !options.chart) throw new IllegalArgumentException('missing options')

  values = options.valuesFile ? "--values ${options.chartsDirectory}/${options.chart}/${options.valuesFile}" : ''
  overrides = options.setOverrides ? "--set ${options.setOverrides.join(' --set ')}" : ''

  sh """
  helm init -c
  helm fetch ${options.chart} --repo ${options.chartsRepository} --untar --untardir ${options.chartsDirectory}
  helm upgrade \
      --install \
      --namespace ${options.namespace} \
      --tiller-namespace ${options.tillerNamespace} \
      ${values} \
      ${overrides} \
      ${options.release} ${options.chartsDirectory}/${options.chart}
  """ 
}