SAP Cloud SDK for JavaScriptを使ってみる （4）CI/CDの設定

##はじめに
この記事は、[SAP Cloud SDK for JavaScriptを使ってみる](https://qiita.com/tami/items/a0280ea080b24f087e68)シリーズの4回目です。
今回は、[Project "Piper"](https://www.project-piper.io/)のライブラリを使って(1)～(3)で作ったプロジェクトをJenkinsのパイプラインでビルド、テスト、デプロイします。

##SAP Cloud SDKで生成されるCI/CDのためのファイル
[CLIでプロジェクトを生成](https://qiita.com/tami/items/2206a478a1ef9998e3b0#1-%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E7%94%9F%E6%88%90)すると、`Jenkinsfile`と`/.pipeline/config.yml`というファイルが作られます。これらがCI/CDのための設定ファイルです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d08b1c81-1666-fd99-8a73-d36fe197c9e2.png)

###【注意】デフォルトの設定は古い！
デフォルトのJenkinsfileは[SAP Cloud SDK Pipeline](https://github.com/SAP/cloud-s4-sdk-pipeline)を使う設定になっています。しかし、[こちら](https://github.com/SAP/cloud-s4-sdk-pipeline/blob/master/gpp-guide.md)のドキュメントによると、今後はSAP Cloud SDK Pipelineは使わずにProject "Piper"のGeneral Purpose Pipelineを使うことが推奨されています。


```:Jenkinsfile
#!/usr/bin/env groovy

/*
 * This file bootstraps the codified Continuous Delivery pipeline for extensions of SAP solutions such as SAP S/4HANA.
 * The pipeline helps you to deliver software changes quickly and in a reliable manner.
 * A suitable Jenkins instance is required to run the pipeline.
 * The Jenkins can easily be bootstraped using the life-cycle script located inside the 'cx-server' directory.
 *
 * More information on getting started with Continuous Delivery can be found in the following places:
 *   - GitHub repository: https://github.com/SAP/cloud-s4-sdk-pipeline
 *   - Blog Post: https://blogs.sap.com/2017/09/20/continuous-integration-and-delivery
 */

/*
 * Set pipelineVersion to a fixed released version (e.g. "v15") when running in a productive environment.
 * To find out about available versions and release notes, visit: https://github.com/SAP/cloud-s4-sdk-pipeline/releases
 */
String pipelineVersion = "master"

library "s4sdk-pipeline-library@${pipelineVersion}"
cloudSdkPipeline(script: this)
```

##[Project "Piper"](https://www.project-piper.io/)について
Project "Piper"には、パイプラインのステップで使うためのライブラリが用意されています。使い方としては、`Jenkinsfile`で実行するステップを定義し、`.pipeline/config.yaml`で全ステップに共通の設定("general" configuration)や、各ステップに渡すパラメータなどの細かい設定("step" configuration)をします。ステップの設定については、`Jenkinsfile`で設定したものが`.pipeline/config.yaml`よりも優先されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ea7bf440-7f6c-e37c-9b96-30c0715482f7.png)
図：[https://www.project-piper.io/configuration/](https://www.project-piper.io/configuration/)より引用

さらに、2つの代表的なパイプラインとして[General Purpose Pipeline](https://www.project-piper.io/stages/introduction/)と[ABAP Environment Pipeline](https://www.project-piper.io/pipelines/abapEnvironment/introduction/)が用意されています。これらはどのようなステージ、ステップを使用するかを指南するベストプラクティスのようなものと私は理解しています。そのまま使えるわけではなく実行したいステップや環境に合わせたパラメータ設定が必要です。

##CI/CDパイプラインの設定
今回は、Prepare、Build、Integration、Deployの4つのステップを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/08132526-b789-a4a6-cf6f-62126ac58887.png)

###.pipeline/config.yamlの一般設定
ここでは、buildToolにnpmを使うという設定をしています。buildToolは以下で出てくるステップのbuildExecuteやcloudFoundryDeployで使われます。generalセクションで設定しておくと、すべてのステップで有効になります。

```
### General project setup
general:
  buildTool: 'npm'
```

###ステージの設定
Jenkinsfileは以下のようになっています。

```:Jenkinsfile
@Library('piper-lib-os') _

node(){
  stage('Prepare')   {
      deleteDir()
      checkout scm
      setupCommonPipelineEnvironment script:this
  }

  stage('Build')   {
    buildExecute script:this, npmRunScripts:['ci-build', 'ci-package']
  }

  stage('Integration') {
    npmExecuteScripts script:this, runScripts:['ci-integration-test']
    testsPublishResults script: this, junit:[pattern: '**/backend-integration/*.xml', updateResults: true, archive: true]
  }

  stage('Deploy')   {
      cloudFoundryDeploy script:this, deployTool: 'cf_native'
  }
}
```
####Prepareステージ

```
  stage('Prepare')   {
      deleteDir()
      checkout scm
      setupCommonPipelineEnvironment script:this
  }
```

最初の二つのステップはJenkinsのプラグインです。

- [deleteDir()](https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/#deletedir-recursively-delete-the-current-directory-from-the-workspace): カレントディレクトリを再帰的に削除する
- [checkout scm](https://www.jenkins.io/doc/pipeline/steps/workflow-scm-step/#checkout-check-out-from-version-control): バージョンコントロールからチェックアウトする

三つ目のステップはProject "Piper"のライブラリステップ[setupCommonPipelineEnvironment](https://www.project-piper.io/steps/setupCommonPipelineEnvironment/)を使用します。このステップは、パイプラインで使われる設定（パラメータ）を初期化します。

####Buildステージ
```
  stage('Build')   {
    buildExecute script:this, npmRunScripts:['ci-build', 'ci-package']
  }
```

ライブラリステップの[buildExecute](https://www.project-piper.io/steps/buildExecute/)を使用します。buildExecuteではビルドで実行するスクリプトを複数指定できます。SAP Cloud SDKのプロジェクトの場合、[buildとpackageという2つのステップが必要](https://qiita.com/tami/items/2206a478a1ef9998e3b0#4-cloud-foundry%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4)です。必要なスクリプトは`package.json`に以下のように定義されているので、それらを指定します。

```json:package.json
  "scripts": {
    ...
    "ci-build": "npm run build",
    "ci-package": "sap-cloud-sdk package --ci",
    "ci-integration-test": "jest --ci --config ./test/jest-e2e.json",
    "ci-backend-unit-test": "jest --ci"
  }
```
####Integrationステージ
```
  stage('Integration') {
    npmExecuteScripts script:this, runScripts:['ci-integration-test']
    testsPublishResults script: this, junit:[pattern: '**/backend-integration/*.xml', updateResults: true, archive: true]
  }
```
一つ目のステップ[npmExecuteScripts](https://www.project-piper.io/steps/npmExecuteScripts/)では、テストで実行するnpmのスクリプトを複数指定することができます。ここでは、e2eテストを実行するためのスクリプトを設定しました。
二つ目のステップ[testsPublishResults](https://www.project-piper.io/steps/testsPublishResults/)は、テスト結果をわかりやすい形で表示するためのステップです。表示する対象は、パターン`**/backend-integration/*.xml`にマッチするファイル（=backend-integrationフォルダの下にある.xmlとつくファイル）です。

※この設定だとテストが失敗したときは二つ目のステップが実行されず、テスト結果が表示されません。失敗した場合もステップを実行する方法がわからなかったため今はこうしています。

####Deployステージ
```
  stage('Deploy')   {
      cloudFoundryDeploy script:this, deployTool: 'cf_native'
  }
```
ライブラリステップの[cloudFoundryDeploy](https://www.project-piper.io/steps/cloudFoundryDeploy/)を使用します。今回は`cf push`コマンドを使ったデプロイのためdeployToolに`cf_native`を指定しています。
さらに、`.pipeline/config.yaml`に以下の設定をしています。設定内容は、Cloud Foundryの認証情報、およびデプロイ先のAPIエンドポイント、組織、スペース情報です。また、manifest.ymlの内容を見てデプロイするので、manifestに`manifest.yml`を指定しています。

```yaml:.pipeline/config.yaml
### General project setup
general:
  ...
### Step-specific configuration
steps:
  cloudFoundryDeploy:
    cloudFoundry:
      credentialsId: 'CF_CREDENTIALS'
      apiEndpoint: 'https://api.cf.eu10.hana.ondemand.com'
      org: 'b736177ctrial'
      space: 'dev'
      manifest: 'manifest.yml'
```

※credentialsIdに指定した'CF_CREDENTIALS'は、JenkinsのCredentialsに登録が必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/9ed1f595-91c8-cd13-3be9-47a78e16c8e1.png)

##CI/CDパイプラインを実行
プロジェクトをGitに格納し、Jenkinsでパイプラインを設定します。
以下はパイプラインの実行結果です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/8f2c87be-3ba8-d540-b3d2-6bf3529fc7b6.png)
Integrationステージで作成したテストの結果が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/2463c909-0271-9802-44ab-6de9e250f674.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/940d9dba-7643-c378-4c99-2ac6094e1f7e.png)

Cloud Foundryにアプリケーションがデプロイされたことが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/1b927a0d-1feb-7ebf-8c74-4e3ada614bdb.png)

##おまけ：テストがエラーの場合もテスト結果を表示する
上記で紹介した設定だと、テストがエラーのときは二つ目のステップが実行されず、テスト結果が表示されません。テスト結果を常に表示するためには、Jenkinsfileの設定を以下のようにします。

参考：[Running multiple steps/Finishing up](https://www.jenkins.io/doc/pipeline/tour/running-multiple-steps/#finishing-up)

```:Jenkinsfile
@Library('piper-lib-os') _

pipeline {
    agent any

    stages {
        stage('Prepare')   {
            steps {
                deleteDir()
                checkout scm
                setupCommonPipelineEnvironment script:this
            }
        }

        stage('Build')   {
            steps {
                buildExecute script:this, npmRunScripts:['ci-build', 'ci-package']
            }            
        }

        stage('Integration') {
            steps {
                npmExecuteScripts script:this, runScripts:['ci-integration-test']
            }
        }

        stage('Deploy')   {
            steps {
                cloudFoundryDeploy script:this, deployTool: 'cf_native', manifest: 'manifest.yml'
            }
        }
    }
    post {
        always {
            testsPublishResults script: this, junit:[pattern: '**/backend-integration/*.xml', updateResults: true, archive: true]
        }
    }
}
```
テストの想定結果を変えて、わざと失敗させます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/0b65a554-ff70-8d87-606d-5e0e531c9724.png)

失敗したテストの結果が表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d914cfdf-dec5-f8fb-8991-8a35aa173ee7.png)