Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Writing Jenkinsfiles #148

Open
gloriaJun opened this issue May 3, 2022 · 5 comments
Open

Writing Jenkinsfiles #148

gloriaJun opened this issue May 3, 2022 · 5 comments
Assignees
Labels

Comments

@gloriaJun gloriaJun added the ci/cd label May 3, 2022
@gloriaJun gloriaJun self-assigned this May 3, 2022
@gloriaJun
Copy link
Owner Author

gloriaJun commented May 3, 2022

Parallel Stages

image

Declarative Pipeline

#!/usr/bin/env groovy

pipeline {
  agent any

  tools {
    nodejs "nodejs12.13.0"
    jdk "jdk11"
  }

  environment {
    HOME = "${env.WORKSPACE}"
    YARN_CACHE_FOLDER = "${env.HOME}/.yarn-cache"

    /**
      defined internal env
    */
    npmCommand = "yarn"
  }

  options {
      buildDiscarder(logRotator(
        artifactDaysToKeepStr: '1',
        artifactNumToKeepStr: '10',
        daysToKeepStr: '3',
        numToKeepStr: "10")
      )
      timestamps()
      timeout(time: 30, unit: 'MINUTES')
  }

  stages {
    stage('Checkout') {
      steps {
        echo "Branch: ${env.BRANCH_NAME}, PrBranch: ${env.CHANGE_BRANCH}"
        sh "which node; npm --version; node --version; yarn -version"
        sh 'which java; java -version; echo $JAVA_HOME'
        sh "printenv"
      }
    }

    stage('Install packages') {
      steps {
        sh "${npmCommand} install"
      }
    }

    stage('Lint') {
      steps {
        sh "${npmCommand} run lint -- -f json -o eslint.json"
      }
    }    

    stage('parallel test') {
      parallel {
        stage('parallel-1') {
          steps {
            script {
              sh "ls -al ${env.JAVA_HOME}/bin" 
              sh "which java; java -version; echo ${env.JAVA_HOME}
            }
          }
        }

        stage('parallel-2') {
          steps {
            script {
              sh "ls -al ${env.JAVA_HOME}/bin" 
              sh "which java; java -version; echo ${env.JAVA_HOME}
            }
          }
        }
      }
    }    
  }
}

Scripted Pipeline

node {
  /**
    set tools
  **/
  env.NODEJS_HOME = tool name: 'nodejs12.13.0'
  env.JAVA_HOME=tool name: 'jdk11'
  env.PATH="${env.JAVA_HOME}/bin:${env.NODEJS_HOME}/bin:${env.PATH}"

  /**
    set cache node_modules
  **/
  env.YARN_CACHE_FOLDER = "${env.WORKSPACE}/.yarn-cache"

  /**
    defined internal env vars
  **/
  npmCommand = "yarn"

  properties([
    // 오래된 빌드 삭제
    buildDiscarder(logRotator(
      artifactDaysToKeepStr: '1',
      artifactNumToKeepStr: '10',
      daysToKeepStr: '3',
      numToKeepStr: '10'
    )), 
  ])

  timestamps {
    timeout(time: 30, unit: 'MINUTES') {
      stage ('Env') {
        echo "Branch: ${env.BRANCH_NAME}, PrBranch: ${env.CHANGE_BRANCH}"
        sh "which node; npm --version; node --version; yarn -version"
        sh "which java; java -version; echo $JAVA_HOME"
        sh "printenv"
      }

      stage ('Checkout') {
        checkout scm
      }  

      stage ('Install Dependencies') {
        sh "${npmCommand} install"
      }      

      stage ('Lint') {
        sh "${npmCommand} run lint -- -f json -o eslint.json"
      }

      stage ('parallel test') {
        def stages = [:]

        stages['parallel-1'] = {
          sh "ls -al ${env.JAVA_HOME}/bin" 
          sh "which java; java -version; echo ${env.JAVA_HOME}" 
        }

        stages['parallel-2'] = {
          sh "ls -al ${env.JAVA_HOME}/bin" 
          sh "which java; java -version; echo ${env.JAVA_HOME}" 
        }

        parallel(stages)
      }         
    }
  }
}

@gloriaJun
Copy link
Owner Author

Declarative Pipeline Examples

#!/usr/bin/env groovy
void setBuildStatus(String context, String message, String state, String url) {
  if (state == 'PENDING') {
    backref = "${env.RUN_DISPLAY_URL}"
  } else {
    backref = url
  }

  // step([
  //   $class: "GitHubCommitStatusSetter",
  //   reposSource: [$class: "ManuallyEnteredRepositorySource", url: "${env.GIT_URL}"],
  //   contextSource: [$class: "ManuallyEnteredCommitContextSource", context: "ci/jenkins/${context}"],
  //   errorHandlers: [[$class: "ChangingBuildStatusErrorHandler", result: "UNSTABLE"]],
  //   statusBackrefSource: [$class: "ManuallyEnteredBackrefSource", backref: backref],
  //   statusResultSource: [ $class: "ConditionalStatusResultSource", results: [[$class: "AnyBuildResult", message: message, state: state]] ]
  // ]);

  // To set publishChecks
  title = 'Build Check'

  if (state == 'PENDING') {
    publishChecks title: title,
      name: context,
      status: 'IN_PROGRESS',
      detailsURL: url
  } else if (state != 'SUCCESS') {
    publishChecks title: title,
      name: context,
      status: 'COMPLETED',
      conclusion: 'FAILURE',
      detailsURL: url
  } else {
    publishChecks title: title,
      name: context,
      detailsURL: url
  }
}

void notifySlack(String message, String color) {
  slackSend (channel: '#*******', color: color, message: message + ": Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}

pipeline {
  agent {
    dockerfile { filename './build/Dockerfile' }
  }

  environment {
    // npm_config_cache = "npm-cache"
    HOME = "${env.WORKSPACE}"
    // NPM_CONFIG_PREFIX = "${env.HOME}/.npm"
    YARN_CACHE_FOLDER = "${env.HOME}/.yarn-cache"

    /**
      defined internal env
    */
    npmCommand = "yarn"

    isDevelop = "${env.BRANCH_NAME ==~ /(develop)/}"
    isMainBranch = "${env.BRANCH_NAME ==~ /(master|develop|release.*)/}"
    isPrBranch= "${env.BRANCH_NAME ==~ /^PR-\d+$/}"
    isSonarCoverage= "${isMainBranch || env.isPrBranch && env.CHANGE_TARGET ==~ /(develop)/}"

    coverageReportDir = "tests/coverages/reports"
    junitReportFilename = 'test-junit-report.xml'
    sonarReportFilename = 'test-sonar-report.xml'

    buildUrl ="${env.BUILD_URL}"
    storybookUrl = "*******"
    // sonarQubeUrl = "http://*******/dashboard?id=*******&${isPrBranch == 'true' ? 'pullRequest=' + env.CHANGE_ID  : 'branch=' + env.BRANCH_NAME.replaceAll('/', '%2F')}"

    repository= "*******"
    githubEndpoint= "*******"
  }

  options {
      buildDiscarder(logRotator(artifactDaysToKeepStr: '1', artifactNumToKeepStr: '10', daysToKeepStr: '3',numToKeepStr: "10"))
      timestamps()
      timeout(time: 30, unit: 'MINUTES')
  }

  stages {
    stage('Checkout') {
      steps {
        echo "Branch: ${env.BRANCH_NAME}, PrBranch: ${env.CHANGE_BRANCH}"
        sh "which node; npm --version; node --version; yarn -version"
        sh 'which java; java -version'
        sh "printenv"
      }
    }

    stage('Install dependencies') {
      steps {
          echo 'Installing dependencies...'
          // sh "npm config set registry *******"
          // sh "npm install"
          // sh "npm ci --prefer-offline --no-audit"
          sh "yarn config set registry *******"
          sh "yarn install"
      }
    }

    stage('Lint') {
      environment {
        context="lint"
      }

      steps {
        script {
          try {
            sh "${npmCommand} run lint -- -f json -o eslint.json"
            sh "${npmCommand} run lint -- -f checkstyle -o checkstyle-eslint.xml"
          } catch (e) {
            sh "${npmCommand} run lint"
          }
        }
      }

      post {
        always {
          recordIssues enabledForFailure: true, aggregatingResults: true, tool: checkStyle(pattern: 'checkstyle-eslint.xml')
        }
      }
    }

    stage('Build') {
      parallel {
        stage('App') {
          environment {
            context="build"
          }

          steps {
            setBuildStatus("${context}", "${context} Progressing...", "PENDING", "${buildUrl}");
            sh "${npmCommand} run build"
          }

          post {
            always {
              script {
                if (currentBuild.currentResult != 'FAILURE') {
                  setBuildStatus("${context}", "${env.STAGE_NAME} Success", "SUCCESS", "${buildUrl}");
                } else {
                  setBuildStatus("${context}", "${env.STAGE_NAME} Failed", "FAILURE", "${buildUrl}");
                }
              }
            }
          }
        }    

        stage('Storybook') {
          when {
            expression { isMainBranch == 'true' }
          }

          environment {
            context="storybook/build"
          }

          steps {
            setBuildStatus("${context}", "${context} Progressing...", "PENDING", "${buildUrl}");
            
            script {
               withCredentials([string(credentialsId: 'ZEPLIN_TOKEN', variable: 'TOKEN')]) {
                sh "STORYBOOK_ZEPLIN_TOKEN=${TOKEN} ${npmCommand} run storybook:build"
               }
            }
          }

          post {
            always {
              script {
                if (currentBuild.currentResult != 'FAILURE') {
                  setBuildStatus("${context}", "${env.STAGE_NAME} Success", "SUCCESS", "${buildUrl}");
                } else {
                  setBuildStatus("${context}", "${env.STAGE_NAME} Failed", "FAILURE", "${buildUrl}");
                }
              }
            }
          }
        }    
      }
    }    

    // stage('Test') {
      // parallel {
        stage('Unit Test') {
          environment {
            context="unit"
          }

          steps {
            sh "${npmCommand} run test:unit:coverage --detectOpenHandles"
          }

          post {
            always {
              script {
                junit "${coverageReportDir}/${context}/${junitReportFilename}"
              }
            }
          }
        }
        
        stage('Storyshot') {
          environment {
            context="storybook/snapshot-test"
          }

          steps {
            // setBuildStatus(context, "${context} Progressing...", "PENDING", "${buildUrl}");
            catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
              script {
                try {
                  // sh "${npmCommand} run test:storybook:coverage"
                  sh "${npmCommand} run test:storybook"
                } catch (e) {
                  // setBuildStatus("${context}", "${context} Failed", "FAILURE", "${buildUrl}");
                  error e.message
                }
              }
            }
          }

          post {
            always {
              script {
                junit "${coverageReportDir}/storybook/${junitReportFilename}"
              }
            }
            // success {
            //   setBuildStatus("${context}", "${context} Pass", "SUCCESS", "${buildUrl}");
            // }
          }
        }
      // }
    // }    

    stage('Report') {
      parallel {
        stage('Coverage Report to CI') {
          steps {
            sh "${npmCommand} run combine-coverage"

            script {
              step([$class: 'CoberturaPublisher', coberturaReportFile: "${coverageReportDir}/combined/cobertura-coverage.xml"])
            }
          }
        }   

        stage('SonarQube Analysis') {
          when {
            expression { isSonarCoverage == 'true' }
          }

          environment {
            scannerHome = tool 'SonarQubeScanner'
          }

          steps {
            script{
              withCredentials([string(credentialsId: '*******', variable: 'TOKEN')]) {
                withSonarQubeEnv('SonarQubeServer') {
                  def args = ''

                  if (isPrBranch == 'true') {
                    args = args + " \
                      -Dsonar.pullrequest.key=${env.CHANGE_ID} \
                      -Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} \
                      -Dsonar.pullrequest.base=${env.CHANGE_TARGET} \
                      -Dsonar.pullrequest.provider=github \
                      -Dsonar.pullrequest.github.repository=${repository} \
                      -Dsonar.pullrequest.github.endpoint=${githubEndpoint} \
                      "
                  } else {
                    args = args + " \
                      -Dsonar.branch.name=${env.BRANCH_NAME} \
                      "
                  } 

                    // -Dsonar.testExecutionReportPaths='${coverageReportDir}/unit/test-sonar-report.xml,${coverageReportDir}/storybook/test-sonar-report.xml' \
                  sh "BROWSERSLIST_IGNORE_OLD_DATA=true ${scannerHome}/bin/sonar-scanner \
                    -Dsonar.projectKey=******* \
                    -Dsonar.projectName=******* \
                    -Dsonar.sources=./src \
                    -Dsonar.exclusions='./build,./cypress,./tests,./src/assets,./src/xlt,./src/lib,./src/config,./src/languages,**/*.stories.js,**/*.stories.tsx,**/__snapshots__,**/*.snap.js' \
                    -Dsonar.tests=./src \
                    -Dsonar.test.inclusions='**/*.test.tsx,**/*.test.ts,**/*.test.js,**/*.spec.ts,**/*.spec.tsx' \
                    -Dsonar.testExecutionReportPaths='${coverageReportDir}/unit/${sonarReportFilename},${coverageReportDir}/storybook/${sonarReportFilename}' \
                    -Dsonar.javascript.lcov.reportPaths=${coverageReportDir}/combined/lcov.info \
                    -Dsonar.eslint.reportPaths=eslint.json \
                    -Dsonar.typescript.tsconfigPath=tsconfig.json \
                    ${args} \
                  "
                }
              }
            }
          }
        }
      }
    }

    stage('Deploy') {
      parallel {
        stage('Storybook image snapshot Test') {
         when {
           expression { isMainBranch == 'true' }
         }

         environment {
           context="storybook/snapshot-image-test"
         }

         steps {
           setBuildStatus(context, "${context} Progressing...", "PENDING", "${buildUrl}");

           catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
             script {
               try {
                 sh "${npmCommand} run test:image-snapshot"
               } catch (e) {
                 setBuildStatus("${context}", "${context} Failed", "FAILURE", "${buildUrl}");
                 error e.message
               }
             }
           }
         }

         post {
           success {
             setBuildStatus("${context}", "${context} Pass", "SUCCESS", "${buildUrl}");
           }
         }
       }

       stage('Deploy Storybook') {
          when {
            expression { isMainBranch == 'true' }
          }

          environment {
            context="storybook/deploy"
            host = "*******"
            outputDir = ".out"
            branchName = "${env.BRANCH_NAME.split("/")[0]}"
            deployTempDir = "/tmp/jenkins_tmp/*******/${env.GIT_BRANCH}"
            deployTargetDir = "~/deploy/storybook-*******/${env.branchName}"
          }

          steps {
            setBuildStatus(context, "${context} Progressing...", "PENDING", "${buildUrl}");

            script {
              def remote = [:]
              remote.name = "*******"
              remote.host = "*******"
              remote.allowAnyHosts = true

              withCredentials([sshUserPrivateKey(credentialsId: 'jenkins-private-key', keyFileVariable: 'identity')]) {
                  remote.user = '*******' 
                  remote.identityFile = identity
                  remote.logLevel = 'INFO' 

                  sshCommand remote: remote, command: "mkdir -p ${deployTempDir}"
                  sshPut remote: remote, from: "./${outputDir}", into: "${deployTempDir}" 
                  sshCommand remote: remote, command: "rsync -avzh ${deployTempDir}/${outputDir}/* ${deployTargetDir}/ --delete"
                  sshRemove remote: remote, path: "${deployTempDir}", failOnError: false
              }
            }
          }

          post {
            always {
              script {
                if (currentBuild.currentResult != 'FAILURE') {
                  setBuildStatus("${context}", "${env.STAGE_NAME} Success", "SUCCESS", "${storybookUrl}/${branchName}");
                } else {
                  setBuildStatus("${context}", "${env.STAGE_NAME} Failed", "FAILURE", "${storybookUrl}/${branchName}");
                }
              }
            }
          }
        }
      }
    }

    stage('SonarQube Quality Gate') {
      when {
        expression { isSonarCoverage == 'true' }
      }

      // environment {
      //   context="SonarQube"
      // }

      steps {
        // setBuildStatus(context, "${context} Progressing...", "PENDING", "${buildUrl}");

        script{
          timeout(time: 10, unit: 'MINUTES') {
            waitForQualityGate abortPipeline: false
            // def qg = waitForQualityGate()

            // if (qg.status != 'OK') {
            //   echo "Pipeline aborted due to quality gate failure: ${qg.status}"
            //   // setBuildStatus("${context}", "${context} Failed", "UNSTABLE", "${sonarQubeUrl}");
            // }
          }
        }
      }

      // post {
      //   always {
      //     script {
      //       if (currentBuild.currentResult != 'FAILURE') {
      //         setBuildStatus("${context}", "${env.STAGE_NAME} Success", "SUCCESS", "${sonarQubeUrl}");
      //       } else {
      //         setBuildStatus("${context}", "${env.STAGE_NAME} Failed", "FAILURE", "${sonarQubeUrl}");
      //       }
      //     }
      //   }
      // }
    }    

   // stage('PR Comment') {
    //   when {
    //     expression { isPrBranch == 'true' }
    //   }

    //   steps {
    //     step([
    //       $class: 'ViolationsToGitHubRecorder', 
    //       config: [
    //         gitHubUrl: 'https://*******.git', 
    //         repositoryOwner: '*******', 
    //         repositoryName: '*******', 
    //         pullRequestId: "${env.CHANGE_ID}", 

    //         credentialsId: 'githubApp',

    //         createCommentWithAllSingleFileComments: true, 
    //         createSingleFileComments: true, 
    //         commentOnlyChangedContent: true, 
    //         commentOnlyChangedFiles: true,
    //         minSeverity: 'INFO',
    //         maxNumberOfViolations: 99999,
    //         keepOldComments: false,

    //         commentTemplate: """
    //           *Reporter**: {{violation.reporter}}{{#violation.rule}}

    //           **Rule**: {{violation.rule}}{{/violation.rule}}
    //           **Severity**: {{violation.severity}}
    //           **File**: {{violation.file}} L{{violation.startLine}}{{#violation.source}}

    //           **Source**: {{violation.source}}{{/violation.source}}

    //           {{violation.message}}
    //           """,

    //         violationConfigs: [
    //          [ pattern: '.*/checkstyle-*\\.xml$', parser: 'CHECKSTYLE', reporter: 'Checkstyle' ], 
    //         //  [ pattern: '.*/findbugsXml\\.xml$', parser: 'FINDBUGS', reporter: 'Findbugs' ], 
    //         //  [ pattern: '.*/pmd\\.xml$', parser: 'PMD', reporter: 'PMD' ], 
    //         ]
    //       ]
    //     ])
    //   }
    // }    
  }

  post {
    cleanup {
      cleanWs(
        deleteDirs: true,
        patterns: [
          [pattern: 'dist', type: 'INCLUDE'],
          [pattern: '.out', type: 'INCLUDE'],
        ]
      )
    }
    success {
      script {
        def previousResult = currentBuild.previousBuild?.result

        if (!previousResult || (previousResult && previousResult != currentBuild.result)) {
          notifySlack ('SUCCESS', '#00FF00')
        }
      }
    }
    unstable {
      notifySlack ('UNSTABLE', '#FFFF00')
    }
    failure {
      notifySlack ('FAILED', '#FF0000')
    }
  }
}

@gloriaJun
Copy link
Owner Author

Scripted Pipeline Examples

@gloriaJun
Copy link
Owner Author

GitHubCommitStatusSetter & publishChecks

def setBuildStatus(String context, String message, String state, String url) {
  if (state == 'PENDING') {
    backref = "${env.RUN_DISPLAY_URL}"
  } else {
    backref = url
  }

  step([
    $class: "GitHubCommitStatusSetter",
    reposSource: [$class: "ManuallyEnteredRepositorySource", url: "${env.GIT_URL}"],
    contextSource: [$class: "ManuallyEnteredCommitContextSource", context: "ci/jenkins/${context}"],
    errorHandlers: [[$class: "ChangingBuildStatusErrorHandler", result: "UNSTABLE"]],
    statusBackrefSource: [$class: "ManuallyEnteredBackrefSource", backref: backref],
    statusResultSource: [ $class: "ConditionalStatusResultSource", results: [[$class: "AnyBuildResult", message: message, state: state]] ]
  ]);

  // To set publishChecks
  // title = 'Build Check'

  // if (state == 'PENDING') {
  //   publishChecks title: title,
  //     name: context,
  //     status: 'IN_PROGRESS',
  //     detailsURL: url
  // } else if (state != 'SUCCESS') {
  //   publishChecks title: title,
  //     name: context,
  //     status: 'COMPLETED',
  //     conclusion: 'FAILURE',
  //     detailsURL: url
  // } else {
  //   publishChecks title: title,
  //     name: context,
  //     detailsURL: url
  // }
}

def setBuildProcessingStatus(String context, String url) {
  setBuildStatus(context, "${context} Progressing...", "PENDING", url)
}

def setBuildResultStatus(String context, Boolean isSuccess, String url) {
  if (isSuccess) {
    setBuildStatus(context, "${context} Success", "SUCCESS", url)
  } else {
    setBuildStatus(context, "${context} Failed", "FAILURE", url)
  }
}

@gloriaJun
Copy link
Owner Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant