Skip to Content

Introduction

In the ABAP world CI/CD was never as popular as nowadays. With the growing amount of UI5 apps, CI/CD is becoming a very hot topic. It’s even possible to use it for ABAP developments thanks to ABAPGit!

I think we all noticed that a lot of SAP customers are not aware of CI/CD, just because it’s simply never being used for ABAP developments. By starting with UI5 apps, they meet a total different world where CI/CD is common sense. For this reason, SAP provided a best practice guide on CI/CD ( https://www.sap.com/developer/tutorials/ci-best-practices-ci-cd.html ) and they are working on a CI/CD service in SCP which is currently in beta. But there are other solutions too.

What is CI/CD?

Let’s start with a brief introduction about CI/CD for those are new to this.

CI stands for Continuous integration and means that developers should be possible to merge code changes as often as possible to the master branch on git. The changes will trigger a build of the project and validate the it with automated tests.

CD can have two meanings:

  • Continuous delivery
    • In most of the cases, they mean this with CD. It is an extension on top of CI to release the new changes to the end-users quickly in a sustainable way. Continuous delivery enables you to deploy your application at any time by just clicking on a release button.
  • Continuous deployment
    • This is very similar to Continuous delivery except, you don’t need to click any button. If the CI process is completed without failing, it will deploy the app automatically.

You can read more on CI/CD here: https://www.atlassian.com/continuous-delivery/ci-vs-ci-vs-cd

In case we’re using CI/CD on an ABAP system for UI5 apps, we could have CI/CD/CD. Let me explain this:

  • CI: Commit and push changes of your UI5 app to the master branch on git from in the SAP Web IDE. This will trigger the build and unit test automatically.
  • Continuous deployment: We could create a grunt task to deploy the UI5 app automatically to the ABAP system. (not completely CD because it’s only deploying the app to the development system)
  • Continuous delivery: The automatic deploy will only deploy the app to the DEV system. For getting the app in production, you need to use the transport management in the ABAP system. This system requires you to import it to production with a button click.

This means that we use all three of them in some way😊

 

CI/CD Solutions

You have multiple options in case you want to start using CI/CD for your UI5 apps. One of the most well-known CI/CD solutions, is the one that has been described by SAP in this best practice guide: https://www.sap.com/developer/tutorials/ci-best-practices-intro.html

Based on this setup, SAP is working on a CI/CD service in SCP that is integrated in the SAP Web IDE. This is currently in beta and not yet general available.

It means we only have the best practice guide for now… but have you tried following this guide? I’ve been down this road and it was a very bumpy one 😊 You have to install different components, do a lot of configuration and not everything is explained in the guide in detail. In the end, everything worked on my system but it took a lot of time. For that reason, I’ve been looking to other solutions for CI/CD.

When you search for CI/CD tools, you’ll find a lot of tools that you can just use online like GitLab, Travis and many more. A lot of these tools are available as cloud tools but we need local installations. For automatic deployment, the CI/CD tool needs access to the ABAP system. Therefore, it has to be in the same network.

Eventually I came across GitLab for on-premise which offers integrated CI/CD for free: https://about.gitlab.com/pricing/

It’s very easy to use and maintain your git repositories. Besides that, it uses the concept “Merge requests” which is like “pull requests” in GitHub.

Setup GitLab

The documentation and community of GitLab is good. You can find an installation guide for all possible Operating Systems. Just follow the steps for your OS here:  https://about.gitlab.com/installation/

Enable HTTPS, this is needed for the SAP Web IDE:

https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https

Besides a GitLab server, you also need to install a GitLab runner. Runners are being used for the CI process. You could install this runner on your local machine and connect it to the server to give it more power. I just installed one runner on the same machine as the GitLab server. (This is not recommended)

Install GitLab runner, I’m using a unix os so I configured my runner as a Shell runner

https://docs.gitlab.com/runner/install/linux-repository.html

Configure runner: https://docs.gitlab.com/ee/ci/runners/

 

Connect GitLab to SAP Web IDE

Create a virtual host for the GitLab server in the SAP Cloud Connector and configure the accessible resources.

Get the certificate of the GitLab server

And export it by clicking on “Copy to File”:

Add the certificate to the trust store of the SAP Cloud Connector

Create a destination in your SCP account

Use the Virtual host in the SAP Web IDE for using your on-premise git:

This is also documented by SAP: https://help.sap.com/viewer/825270ffffe74d9f988a0f0066ad59f0/Cloud/en-US/b8427ec16ae64347b97d2d46fb28f7cd.html

Setup CI/CD in GitLab for UI5

This is where the fun starts! 🙂

Steps (Pipeline) config – yml file

In GitLab, you can configure CI/CD by adding a “.gitlab-ci.yml” file to your project. In this file, you can configure all the steps and assign tasks to each step. From the moment that you have added this file, it will run the CI process on each push command on each branch (depending on your config). I have defined 5 steps in this yml file for my CI/CD:

  1. Initializations
    1. This will load required npm modules and store them for the other steps
    2. For storing these npm modules, I’m using the syntax “artifacts”
  2. Linting test
    1. This will check for errors in the project, it will stop the flow in case of any error
    2. It’s based on the same linting that’s being used in the SAP Web IDE
    3. Calls grunt task lint. Normally this task doesn’t stop in case of errors. I wrapped a task around it to check the result of the lint task. If I find an error in this result of the lint task, it will stop the process
  3. Unit test
    1. Run unit test
    2. This will execute the unit-test grunt task
  4. Build – create a build for the app
    1. Runs the build grunt task
  5. Deploy – deploy to the ABAP system
    1. Deploy grunt task

I’m doing the unit test before the build, that way I don’t have to wait for the build in case the test would already fail. I’m planning to add a unit test step after the build on the “dist” folder to be sure that the app still works.

For deploying to ABAP, the Grunt deploy task needs to know which system, package and app name we want to deploy the app in. Therefore, I store all these parameters in the GitLab CI/CD settings of the repo. (see below)

It’s possible to define on which branch each task should be executed. It will be executed for each new change on each branch by default. But you could also define a specific branch for a step. For example, the deployment step should only be executed on the master branch. Therefore, I’ve added “only:  – master”

The syntax “artifacts” and “dependencies” are used for sharing resources between different steps. Otherwise it would need to load the node modules in each step, which would be overkill.

This is how I configured my yml file:

image: node:latest

stages:
  - init
  - validation
  - build
  - test
  - deploy
 
before_script:
  - pwd
  - whoami
  - echo "$CI_BUILD_REPO"
  - echo "$CI_BUILD_NAME"
  - echo "$CI_PIPELINE_ID"
  - echo "$CI_PIPELINE_IID"
  - echo "$CI_COMMIT_REF_SLUG"

init-ci:
  stage: init
  script:
    - sudo npm install
    - node_modules/grunt-cli/bin/grunt --verbose clean
  artifacts:
    paths:
    - node_modules/

code-validation: 
   stage: validation 
   script: 
     - node_modules/grunt-cli/bin/grunt -d -v fiori-test
   dependencies:
    - init-ci

unit-test: 
   stage: test 
   script: 
     - node_modules/grunt-cli/bin/grunt -d -v unit-test
   dependencies:
    - init-ci
    
build-app: 
   stage: build 
   script: 
     - node_modules/grunt-cli/bin/grunt --verbose buildapp
   dependencies:
    - init-ci
   artifacts:
    paths:
    - dist/

deploy-abap: 
   stage: deploy 
   script: 
     - node_modules/grunt-cli/bin/grunt --verbose deploy
   dependencies:
    - init-ci
    - build-app
   only:
    - master

Grunt config

The CI process works very close with the grunt file. It just runs grunt tasks in a defined sequence  (at least in our case).

For my gruntfile, I started from the sapui5 best practice grunt config and extended it with:

  • Fiori test task
    • Thist task will run the lint task and check for Errors in the JavaScript code like the SAP Web IDE. The lint task will only log the result in the dist folder. I also wanted to use this to stop the build process. Therefore, I’ve added a task “check-lint” that will check the result and stop the CI process in case of an error.
  • Unit test task
    • I used the same config for this task like it’s defined in this sample OpenUI5 app: https://github.com/SAP/openui5-sample-app
    • In this task, we need to define the path to the “sap-ui-core.js”. Because the GitLab CI process is running on your on-premise environment, you need to use the path on your on-premise gateway service:
  • Deploy task

This is my gruntfile:

/* global process:true */
"use strict";
module.exports = function (grunt) {
	// Project properties
	var webAppDir = "webapp";
	var targetDir = "dist";
	var preloadPrefix = "be/wl/DemoMulitLabels";
	var namespace = "be.wl.DemoMulitLabels";
	var abapDevelopmentUser = process.env.ABAP_DEVELOPMENT_USER;
	var abapDevelopmentPassword = process.env.ABAP_DEVELOPMENT_PASSWORD;
	var abapDevelopmentServerHost = process.env.ABAP_DEVELOPMENT_SERVER_HOST;
	var abapApplicationName = process.env.ABAP_APPLICATION_NAME;
	var abapApplicationDesc = process.env.ABAP_APPLICATION_DESC;
	var abapPackage = process.env.ABAP_PACKAGE;
	var gitCommit = process.env.CI_COMMIT_TITLE;
	var resourceroots = {};
	resourceroots[namespace] = './base';
	var preprocessorsWebAppDir = {};
	preprocessorsWebAppDir['{' + webAppDir + ',' + webAppDir + '/!(test)}/*.js'] = ['coverage'];

	var config = {
		eslint: {
			options: {
				configFile: ".eslintrc.js"
			},
			target: [webAppDir + "/**/*.js"]
		},
		nwabap_ui5uploader: {
			options: {
				conn: {
					server: abapDevelopmentServerHost
				},
				auth: {
					user: abapDevelopmentUser,
					pwd: abapDevelopmentPassword
				}
			},
			upload_build: {
				options: {
					ui5: {
						package: abapPackage,
						bspcontainer: abapApplicationName,
						bspcontainer_text: abapApplicationDesc,
						create_transport: true,
						transport_use_user_match: true,
						transport_text: gitCommit
					},
					resources: {
						cwd: targetDir,
						src: "**/*.*"
					}
				}
			}
		},
		karma: {
			options: {
				// base path that will be used to resolve all patterns (eg. files, exclude)
				client: {
					openui5: {
						config: {
							theme: 'sap_belize',
							language: 'EN',
							bindingSyntax: 'complex',
							compatVersion: 'edge',
							preload: 'async',
							resourceroots: resourceroots
						},
						tests: [
							preloadPrefix + '/test/unit/allTests',
						]
					}
				},
				browserNoActivityTimeout: '30000',
				frameworks: ['qunit', 'openui5'],
				openui5: {
					path: "https://<<your-gateway-system>>/sap/public/bc/ui5_ui5/1/resources/sap-ui-core.js" // eslint-disable-line
				},
				files: [{
					pattern: '**',
					included: false,
					served: true,
					watched: true
				}],
				reporters: ['progress'],
				port: 9876,
				logLevel: 'DEBUG',
				browserConsoleLogOptions: {
					level: 'warn'
				},
				browsers: ['Chrome'],
				coverageReporter: {
					includeAllSources: true,
					reporters: [{
						type: 'html',
						dir: '../coverage/'
					}, {
						type: 'text'
					}],
					check: {
						each: {
							statements: 100,
							branches: 100,
							functions: 100,
							lines: 100
						}
					}
				}
			},
			src: {
				basePath: webAppDir,
				singleRun: true,
				browsers: ['PhantomJS'],
				preprocessors: preprocessorsWebAppDir,
				reporters: ['progress', 'coverage']
			}
		}
	}; 
	grunt.loadNpmTasks("grunt-eslint");
	grunt.loadNpmTasks("grunt-nwabap-ui5uploader");
	grunt.loadNpmTasks("@sap/grunt-sapui5-bestpractice-build");
	grunt.loadNpmTasks('grunt-openui5');
	grunt.loadNpmTasks('grunt-karma');

	grunt.config.merge(config);

	grunt.registerTask("check-lint", "Check validation", function () {
		var validation = grunt.file.readJSON(targetDir + "/di.code-validation.core_issues.json"),
			hasErrors = false;
		for (var check in validation.results) {
			grunt.log.writeln("Result for: " + check);
			for (var file in validation.results[check].issues) {
				validation.results[check].issues[file].forEach(function (error) {
					if (error.severity === "error") {
						grunt.log.error(error.path + "(" + error.line + "," + error.column + ") : " + error.message).error();
						hasErrors = true;
					}
				});
			}
		}
		if (hasErrors) {
			grunt.fail.warn('Errors found during code validation');
		}
	});
	grunt.registerTask('unit-test', ['karma:src']);
	grunt.registerTask("fiori-test", ["lint", "check-lint"]);
	grunt.registerTask("buildapp", ["build"]);
	grunt.registerTask("deploy", ["nwabap_ui5uploader"]);
};

ESLint config

The project contains “eslint” files, which are used for the lint task. The “eslint” files are generated by the SAP Web IDE. I don’t think they are really needed, but you can just add them to be sure by following these steps:

  • Open Project Settings

  • Go to Code Checking -> Javascript -> Save

This will generate the files with all the eslint rules.

 

CI/CD Global Variables

The CI/CD process requires some information for the deployment step. In GitLab, we have the possibility to use global variables to define repository specific information like BSP app name, package, …  These global variables can be used in the GruntFile.These Global Variables can be found here:

CI/CD global variables for the grunt files:

  • ABAP_APPLICATION_DESC: We need to know a description for creating the BSP
  • ABAP_APPLICATION_NAME: Name for the BSP application in SAP
    • The transport will be generated the first time of the deploy. It will use the same transport from the moment that there exists one.
  • ABAP_DEVELOPMENT_PASSWORD: User password that’s being used for the deployment
  • ABAP_DEVELOPMENT_SERVER_HOST: Server to where you want to deploy the app
  • ABAP_DEVELOPMENT_USER: User that’s being used for the deployment
  • ABAP_PACKAGE: Package where the app needs to be located

Result

When everything is configured correctly, each push to git will trigger the CI process and you would see something like this in the CI/CD pipeline of your repo:

Here you can find my full GitLab demo project: https://github.com/lemaiwo/GitLab-CI-UI5-DemoApp

 

Conclusion

There are other ways to setup CI/CD! In case you want to use GitLab, just follow these steps:

  • Install GitLab
  • Create a repo
  • Add the “.gitlab-ci.yml” file
  • Add the grunt file
  • Activate the Lint files
  • Configure the CI/CD service in git

And you should be up and running!

To report this post you need to login first.

5 Comments

You must be Logged on to comment or reply to a post.

  1. Joao Sousa

    Hi,

    Do you really need Gitlab on premise when you have the runner inside your network? I have a landscape that runs on VSTS (Cloud) but I use the build runners inside the network, so the build package can be deployed to the on-premise target without problems.

    Since the Build runner is the one that starts the connection to the cloud (via proxy), usually there are no problems with this setup?

    I have no experience with Gitlab, just VSTS.

     

    (1) 
    1. Wouter Lemaire
      Post author

      You’re correct. But, in my case we’re not able to install software on our computer of the customer… that’s also the reason that I’ve installed the runner on the server.

       

      kr, Wouter

      (0) 

Leave a Reply