1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-09-16 09:26:22 +02:00

Initial version of piper-lib.

This commit is contained in:
Oliver Nocon
2017-07-11 15:12:03 +02:00
committed by Marcus Holl
parent 4939e13c6f
commit 47c195805f
51 changed files with 3442 additions and 0 deletions

13
.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

16
.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
.idea/
.settings
logs
reports
*.class
*.iml
.classpath
.project
*~
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
target/
targets/
documentation/docs-gen

151
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,151 @@
# Guidance on how to contribute
All contributions to this project will be released SAP internally AS IS WITHOUT WARRANTY OF ANY KIND.
By submitting a pull request or filing a bug, issue, or feature request, you are agreeing to comply with the waiver.
There are two primary ways to help:
* Using the issue tracker, and
* Changing the code-base.
## Using the issue tracker
Use the issue tracker to suggest feature requests, report bugs, and ask questions. This is also a great way to connect with the developers of the project as well as others who are interested in this solution.
Use the issue tracker to find ways to contribute. Find a bug or a feature, mention in the issue that you will take on that effort, then follow the Changing the code-base guidance below.
## Changing the code-base
Generally speaking, you should fork this repository, make changes in your own fork, and then submit a pull-request. All new code should have thoroughly tested to validate implemented features and the presence or lack of defects and it should come with an adequate documentation.
### Tests
All pipeline library coding MUST come with an automated test. as well as adequate documentation.
### Documentation
The contract of functionality exposed by a library functionality needs to be documented, so it can be properly used.
Implementation of a functionality and its documentation shall happen within the same commit(s).
### Code Style
The code should follow any stylistic and architectural guidelines prescribed by the project. In the absence of guidelines, mimic the styles and patterns in the existing code-base.
Variables, methods, types and so on shall have meaningful self describing names. Doing so makes understanding code easier and requires less commenting. It helps people who did not write the code to understand it better.
Code shall contain comments to explain the intention of the code when it is unclear what the intention of the author was. In such cases, comments should describe the "why" and not the "what" (that is in the code already).
#### EditorConfig
To ensure a common file format, there is a `.editorConfig` file [in place](.editorconfig). To respect this file, [check](http://editorconfig.org/#download) if your editor does support it natively or you need to download a plugin.
### Commit Message Style
Write [meaningful commit messages](http://who-t.blogspot.de/2009/12/on-commit-messages.html) and [adhere to standard formatting](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
Good commit messages speed up the review process and help to keep this project maintainable in the long term.
## Code Style
The intention of this section is to describe the code style for this project. As reference document, the [Groovy's style guide](http://groovy-lang.org/style-guide.html) was taken. For further reading about Groovy's syntax and examples, please refer to this guide.
This project is intended to run in Jenkins [[2]](https://jenkins.io/doc/book/getting-started/) as part of a Jenkins Pipeline [[3]](https://jenkins.io/doc/book/pipeline/). It is composed by Jenkins Pipeline's syntax, Groovy's syntax and Java's syntax.
Some Groovy's syntax is not yet supported by Jenkins. It is also the intention of this section to remark which Groovy's syntax is not yet supported by Jenkins.
As Groovy supports 99% of Java’s syntax [[1]](http://groovy-lang.org/style-guide.html), many Java developers tend to write Groovy code using Java's syntax. Such a developer should also consider the following code style for this project.
### Omit semicolons
### Use the return keyword
In Groovy it is optional to use the _return_ keyword. Use explicitly the _return_ keyword for better readability.
### Use def
When using _def_ in Groovy, the type is Object. Using _def_ simplifies the code, for example imports are not needed, and therefore the development is faster.
### Do not use a visibility modifier for public classes and methods
By default, classes and methods are public, the use of the public modifier is not needed.
### Do not omit parentheses for Groovy methods
In Groovy is possible to omit parentheses for top-level expressions, but [Jenkins Pipeline's syntax](https://jenkins.io/doc/book/pipeline/syntax/) use a block, specifically `pipeline { }` as top-level expression [[4]](https://jenkins.io/doc/book/pipeline/syntax/). Do not omit parenthesis for Groovy methods because Jenkins will interpret the method as a Pipeline Step. Conversely, do omit parenthesis for Jenkins Pipeline's Steps.
### Omit the .class suffix
In Groovy, the .class suffix is not needed. Omit the .class suffix for simplicity and better readability.
e.g. `new ExpectedException().expect(AbortException.class)`
--> `new ExpectedException().expect(AbortException)`
### Omit getters and setters
When declaring a field without modifier inside a Groovy bean, the Groovy compiler generates a private field and a getter and setter.
### Do not initialize beans with named parameters
Do not initialize beans with named parameters, because it is not supported by Jenkins:
e.g. `Version javaVersion = new Version( major: 1, minor: 8)`
Initialize beans using Java syntax:
e.g. `Version javaVersion = new Version(1, 8)`
Use named parameters for Jenkins Pipeline Steps:
e.g. `sh returnStdout: true, script: command`
### Do not use _with()_ operator
The _with_ operator is not yet supported by Jenkins, and it must not be used or encapsulated in a @NonCPS method.
### Use _==_ operator
Use Groovy’s `==` instead of Java `equals()` to avoid NullPointerExceptions. To compare the references of objects, instead of `==`, you should use `a.is(b)` [[1]](http://groovy-lang.org/style-guide.html).
### Use GStrings
In Groovy, single quotes create Java Strings, and double quotes can create Java Strings or GStrings, depending if there is or not interpolation of variables [[1]](http://groovy-lang.org/style-guide.html). Using GStrings variable and string concatenation is more simple.
#### Do not use curly braces {} for variables or variable.property
For variables, or variable.property, drop the curly braces:
e.g. `echo "[INFO] ${name} version ${version.version} is installed."`
--> `echo "[INFO] $name version $version.version is installed."`
#### Use 'single quotes' for Strings and constants.
#### Use "double quotes" for GStrings.
#### Use '''triple single quotes''' for multiline Strings.
#### Use """triple double quotes""" for multiline GStrings.
#### Use /slash/ for regular expresions.
This notation avoids to double escape backslashes, making easier working with regex.
### Use native syntax for data structures
Use the native syntax for data structures provided by Groovy like lists, maps, regex, or ranges of values.
### Use aditional Groovy methods
Use the additional methods provided by Groovy to manipulate String, Files, Streams, Collections, and other classes.
For a complete description of all available methods, please read the GDK API [[5]](http://groovy-lang.org/groovy-dev-kit.html).
### Use Groovy's switch
Groovy’s switch accepts any kind of type, thereby is more powerful. In this case, the use of _def_ instead of a type is necessary.
### Use alias for import
In Groovy, it is possible to assign an alias to imported packages. Use alias for imported packages to avoid the use of fully-qualified names and increase readability.
### Use Groovy syntax to check objects
In Groovy a null, void, equal to zero, or empty object evaluates to false, and if not, evaluates to true. Instead of writing null and size checks e.g. `if (name != null && name.length > 0) {}`, use just the object `if (name) {}`.
### Use _?._ operator
Use the safe dereference operator _?._, to simplify the code for accessing objects and object members safely. Using this operator, the Groovy compiler checks null objects and null object members, and returns _null_ if the object or the object member is null and never throws a NullPointerException.
### Use _?:_ operator
Use Elvis operator _?:_ to simplify default value validations.
### Use _any_ keyword
If the type of the exception thrown inside a try block is not important, catch any exception using the _any_ keyword.
### Use _assert_
To check parameters, return values, and more, use the assert statement.
## Reference
[1] Groovy's syntax: [http://groovy-lang.org/style-guide.html](http://groovy-lang.org/style-guide.html)
[2] Jenkins: [https://jenkins.io/doc/book/getting-started/](https://jenkins.io/doc/book/getting-started/)
[3] Jenkins Pipeline: [https://jenkins.io/doc/book/pipeline/](https://jenkins.io/doc/book/pipeline/)
[4] Jenkins Pipeline's syntax: [https://jenkins.io/doc/book/pipeline/syntax/](https://jenkins.io/doc/book/pipeline/syntax/)
[5] GDK: Groovy Development Kit: [http://groovy-lang.org/groovy-dev-kit.html](http://groovy-lang.org/groovy-dev-kit.html)

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
# Project "Piper" Overview
An efficient software development process is vital for success in building business applications on SAP Cloud Platform or SAP on-premise platforms.
SAP addresses this need for efficiency with project "Piper". The goal of project "Piper" is to substantially ease setting up continuous deployment processes for the most important SAP technologies by means of Jenkins pipelines.
Project "Piper" consists of two parts:
* [A shared library][piper-library] containing steps and utilities that are required by Jenkins pipelines.
* A set of [Jenkins pipelines][piper-pipelines] using the piper library to implement best practice processes.
# Please follow [this link to our documentation][piper-library-pages]
[piper-library]: https://github.com/SAP/jenkins-library
[piper-pipelines]: https://github.com/SAP/jenkins-pipelines
[piper-library-pages]: https://sap.github.io/jenkins-library

View File

@@ -0,0 +1,7 @@
.md-typeset a {
color: #448aff !important;
}
.md-typeset a:not(.headerlink):hover {
text-decoration: underline;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,71 @@
# Project "Piper" Overview
An efficient software development process is vital for success in building business applications on SAP Cloud Platform or SAP on-premise platforms.
SAP addresses this need for efficiency with project "Piper". The goal of project "Piper" is to substantially ease setting up continuous deployment processes for the most important SAP technologies by means of Jenkins pipelines.
Project "Piper" consists of two parts:
* A [shared library][piper-library] containing steps and utilities that are required by Jenkins pipelines.
* A set of [Jenkins pipelines][piper-pipelines] using the piper library to implement best practice processes as code.
## What you get
The shared library contains all the necessary steps to run our best practice [Jenkins pipelines][piper-pipelines].
!!! note "Jenkins 2.0 Pipelines as Code"
The best practice pipelines are based on the general concepts of [Jenkins 2.0 Pipelines as Code][jenkins-doc-pipelines].
With that you have the power of the Jenkins community at hand to optimize your pipelines.
You can run the best practice Jenkins pipelines out of the box, take them as a starting point for project-specific adaptations or implement your own pipelines from scratch using the shared library.
## Installation
Prerequisites:
* Installation of Jenkins v 2.60.3 or higher running on Linux. We tested with debian-stretch.
* Jenkins Plugins installed as described in the [Required Plugin](jenkins/requiredPlugins) section.
* A Jenkins user with administration privileges.
* The Jenkins instance has access to [github.com](https://github.com).
To setup the shared library, you need to perform the following steps:
1. Login to your Jenkins instance with administration privileges.
1. Open the system configuration page (*Manage Jenkins > Configure System*).
1. Scroll down to section *Global Pipeline Libraries* and add a new Library by clicking the *Add* button.
1. set *Library Name* to `piper-library-os`
1. set *Default Version* to the branch or tag you want to consume (e.g. `master` or `v0.1`)
1. set *Retrieval Method* to `Modern SCM`
1. set *Source Code Management* to `Git`
1. set *Project Repository* to `https://github.com/SAP/jenkins-library`
1. Save changes
![Library Setup](images/setupInJenkins.png)
Now the library is available as `piper-library-os` and can be used in any `Jenkinsfile` by adding this line:
```
@Library('piper-library-os') _
```
## Extensibility
If you consider adding additional capabilities to your `Jenkinsfile`, consult the [Jenkins Pipeline Steps Reference][jenkins-doc-steps].
There, you get an overview about steps that are natively supported by Jenkins.
The [Jenkins shared libraries][jenkins-doc-libraries] concept helps you to extract reusable parts from your pipeline and to keep your pipeline code small and easy to maintain.
!!! tip
If you consider adding custom library steps you can do so using a custom library according to the [Jenkins shared libraries][jenkins-doc-libraries] concept instead of adding groovy coding to the `Jenkinsfile`.
Your custom library can coexist next to the provided pipeline library.
## Community & Support
In the [GitHub repository of the shared library][piper-library] you can find a list of GitHub issues for known bugs or planned future improvements.
Feel free to open new issues for feature requests, bugs or general feedback.
[piper-library]: https://github.com/SAP/jenkins-library
[piper-pipelines]: https://github.com/SAP/jenkins-pipelines
[jenkins-doc-pipelines]: https://jenkins.io/solutions/pipeline
[jenkins-doc-libraries]: https://jenkins.io/doc/book/pipeline/shared-libraries
[jenkins-doc-steps]: https://jenkins.io/doc/pipeline/steps
[jenkins-plugin-sharedlibs]: https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Shared+Groovy+Libraries+Plugin

View File

@@ -0,0 +1,57 @@
# Required Plugins
The following Jenkins plugins are needed in order to use the Piper Library.
The list below contains the plugin Id and version of the plugin.
**Plugins**
- ace-editor 1.1
- authentication-tokens 1.3
- bouncycastle-api 2.16.2
- branch-api 2.0.14
- cloudbees-folder 6.2.1
- credentials 2.1.16
- credentials-binding 1.13
- display-url-api 2.1.0
- docker-commons 1.9
- docker-workflow 1.10
- durable-task 1.15
- git 3.6.2
- git-client 2.5.0
- git-server 1.7
- handlebars 1.1.1
- icon-shim 2.0.3
- jquery-detached 1.2.1
- junit 1.21
- mailer 1.20
- matrix-project 1.12
- momentjs 1.1.1
- pipeline-build-step 2.5.1
- pipeline-graph-analysis 1.3
- pipeline-input-step 2.8
- pipeline-milestone-step 1.3.1
- pipeline-model-api 1.2.2
- pipeline-model-definition 1.1.1
- pipeline-model-extensions 1.1.1
- pipeline-rest-api 2.6
- pipeline-stage-step 2.2
- pipeline-stage-tags-metadata 1.2.2
- pipeline-stage-view 2.6
- pipeline-utility-steps 1.3.0
- plain-credentials 1.4
- scm-api 2.2.3
- script-security 1.34
- ssh-credentials 1.13
- structs 1.10
- workflow-aggregator 2.5
- workflow-api 2.23.1
- workflow-basic-steps 2.6
- workflow-cps 2.41
- workflow-cps-global-lib 2.7
- workflow-durable-task-step 2.17
- workflow-job 2.12.2
- workflow-multibranch 2.14
- workflow-scm-step 2.6
- workflow-step-api 2.13
- workflow-support 2.16

View File

@@ -0,0 +1,62 @@
# FileUtils
## Description
Provides file system related utility functions.
## Constructor
Since there are only static utility methods there is no need for instantiating objects.
## Method Details
### validateDirectory(dir)
#### Description
Checks whether a file exists and is a directory.
#### Parameters
* `dir` - directory to be checked. In case it is relative path it is checked against the
current working directory. In case of doubt use the absolute path (prefix the directory with `pwd`).
#### Return value
none
#### Side effects
none
#### Exceptions
* `IllegalArgumentException`: If the parameter `dir` is null or empty.
* `AbortException`: If the directory does not exist or is not a directory.
#### Example
```groovy
FileUtils.validateDirectory('/path/to/dir')
```
### validateDirectoryIsNotEmpty(dir)
#### Description
Check whether a directory is not empty. Before the directory is checked, `validateDirectory(dir)` is executed.
#### Parameters
* `dir` - directory to be checked. In case it is relative path it is checked against the
current working directory. In case of doubt use the absolute path (prefix the directory with `pwd`).
#### Return value
none
#### Side effects
none
#### Exceptions
* `IllegalArgumentException`: If the parameter `dir` is null or empty.
* `AbortException`: If the directory does not exist or is not a directory or the directory is empty.
#### Example
```groovy
FileUtils.validateDirectoryIsNotEmpty('/path/to/dir')
```

View File

@@ -0,0 +1,75 @@
# Utils
## Description
Provides utility functions.
## Constructors
### Utils()
Default no-argument constructor. Instances of the Utils class does not hold any instance specific state.
#### Example
```groovy
new Utils()
```
## Method Details
### getMandatoryParameter(Map map, paramName, defaultValue)
#### Description
Retrieves the parameter value for parameter `paramName` from parameter map `map`. In case there is no parameter with the given key contained in parameter map `map` `defaultValue` is returned. In case there no such parameter contained in `map` and `defaultValue` is `null` an exception is thrown.
#### Parameters
* `map` - A map containing configuration parameters.
* `paramName` - The key of the parameter which should be looked up.
* `defaultValue` - The value which is returned in case there is no parameter with key `paramName` contained in `map`.
#### Return value
The value to the parameter to be retrieved, or the default value if the former is `null`, either since there is no such key or the key is associated with value `null`. In case the parameter is not defined or the value for that parameter is `null`and there is no default value an exception is thrown.
#### Side effects
none
#### Exceptions
* `Exception`: If the value to be retrieved and the default value are both `null`.
#### Example
```groovy
def utils = new Utils()
def parameters = [DEPLOY_ACCOUNT: 'deploy-account']
assert utils.getMandatoryParameter(parameters, 'DEPLOY_ACCOUNT', null) == 'deploy-account'
assert utils.getMandatoryParameter(parameters, 'DEPLOY_USER', 'john_doe') == 'john_doe'
```
### retrieveGitCoordinates(script)
#### Description
Retrieves the git-remote-url and git-branch. The parameters 'GIT_URL' and 'GIT_BRANCH' are retrieved from Jenkins job configuration. If these are not set, the git-url and git-branch are retrieved from the same repository where the Jenkinsfile resides.
#### Parameters
* `script` The script calling the method. Basically the `Jenkinsfile`. It is assumed that the script provides access to the parameters defined when launching the build, especially `GIT_URL`and `GIT_BRANCH`.
#### Return value
A map containing git-url and git-branch: `[url: gitUrl, branch: gitBranch]`
## Exceptions
* `AbortException`: if only one of `GIT_URL`, `GIT_BRANCH` is set in the Jenkins job configuration.
#### Example
```groovy
def gitCoordinates = new Utils().retrieveGitCoordinates(this)
def gitUrl = gitCoordinates.url
def gitBranch = gitCoordinates.branch
```

View File

@@ -0,0 +1,161 @@
# Version
## Description
Handles version numbers.
## Constructors
### Version(major, minor, patch)
#### Parameters
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
| major | yes | | |
| minor | yes | | |
| patch | no | `-1` | |
* `major` - the major version number.
* `minor` - the minor version number.
* `patch` - the patch version number.
#### Exceptions
* `IllegalArgumentException`: If the `major` or `minor` version number is less than `0`.
#### Example
```groovy
def toolVersion = new Version(1, 2, 3)
```
### Version(text)
#### Parameters
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
| text | yes | | |
* `text` - as an alternative to calling the constructor with `major`, `minor`, and `patch` version numbers, you can pass this as a String of format 'major.minor.patch'.
#### Exceptions
* `IllegalArgumentException`: If the `text` parameter is `null` or empty.
* `AbortException`: If the version `text` has an unexpected format.
#### Example
```groovy
def toolVersion = new Version('1.2.3')
```
## Method Details
### equals
#### Description
Indicates whether some other version instance is equal to this one. The two versions are considered equal when they have the same `major`, `minor` and `patch` version number.
#### Parameters
* `version` - the Version instance to compare to this Version instance.
#### Return value
`true` if `major`, `minor` and `patch` version numbers are equal to each other. Otherwise `false`.
#### Side effects
none
#### Exceptions
* `AbortException`: If the parameter `version` is `null`.
#### Example
```groovy
assert new Version('1.2.3').equals(new Version('1.2.3'))
```
### isCompatibleVersion
#### Description
Checks whether a version is compatible. Two versions are compatible if the major version number is the same, while the minor and patch version number are the same or higher.
#### Parameters
* `version` - the Version instance to compare to this Version instance.
#### Return value
`true` if this Version instance is compatible to the other Version instance. Otherwise `false`.
#### Side effects
none
#### Exceptions
* `AbortException`: If the parameter `version` is `null`.
#### Example
```groovy
assert new Version('1.2.3').isCompatibleVersion(new Version('1.3.1'))
```
### isHigher
#### Description
Checks whether this Version instance is higher than the other Version instance.
#### Parameters
* `version` - the Version instance to compare to this Version instance.
#### Return value
`true` if this Version instance is higher than the other Version instance. Otherwise `false`.
#### Side effects
none
#### Exceptions
* `AbortException`: If the parameter `version` is `null`.
#### Example
```groovy
assert new Version('1.2.3').isHigher(new Version('1.1.6'))
```
### toString
#### Description
Print the version number in format '<major>.<minor>.<patch>'. If no patch version number exists the format is '<major>.<minor>'.
#### Parameters
none
#### Return value
A String consisting of `major`, `minor` and if available `patch`, separated by dots.
#### Side effects
none
#### Exceptions
none
#### Example
```groovy
assert "${new Version('1.2.3')}" == "1.2.3"
```

View File

@@ -0,0 +1,141 @@
# commonPipelineEnvironment
## Description
Provides project specific settings.
## Prerequisites
none
## Method details
### getConfigProperties()
#### Description
Returns the map of project specific configuration properties. No defensive copy is created.
Write operations to the map are visible further down in the pipeline.
#### Parameters
none
#### Return value
A map containing project specific configuration properties.
#### Side effects
none
#### Exceptions
none
#### Example
```groovy
commonPipelineEnvironment.getConfigProperties()
```
### setConfigProperties(configuration)
#### Description
Sets the map of configuration properties. An existing map is overwritten.
#### Parameters
* configuration - A map containing the new configuration
#### Return value
none
#### Side effects
none
#### Exceptions
none
#### Example
```groovy
commonPipelineEnvironment.setConfigProperties([DEPLOY_HOST: 'deploy-host.com', DEPLOY_ACCOUNT: 'deploy-account'])
```
### getConfigProperty(key)
#### Description
Gets a specific value from the configuration property.
#### Parameters
* key - The key of the property.
#### Return value
* The value associated with key `key`. `null` is returned in case the property does not exist.
#### Side effects
none
#### Exceptions
none
#### Example
```groovy
commonPipelineEnvironment.getConfigProperty('DEPLOY_HOST')
```
### setConfigProperty(key, value)
#### Description
Sets property `key` with value `value`. Any existing property with key `key`is overwritten.
#### Parameters
* `key` The key
* `value` The value
#### Return value
none
#### Side effects
none
#### Exceptions
none
#### Example
```groovy
commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'my-deploy-host.com')
```
### getMtarFileName()
#### Description
Returns the name of the mtar file.
#### Parameters
none
#### Side effects
none
#### Exceptions
none
#### Example
```groovy
commonPipelineEnvironment.getMtarFileName()
```
### setMtarFileName(name)
#### Description
Sets the name of the mtar file. Any old value is discarded.
#### Parameters
The name of the mtar file name.
#### Side effects
none
#### Exceptions
none
#### Example
```groovy
commonPipelineEnvironment.setMtarFileName('foo')
```

View File

@@ -0,0 +1,26 @@
# getConfigProperties
## Description
Gets a `map` of configuration properties stored in the `commonPipelineEnvironment` object.
## Parameters
none
## Return values
A `map` of the current configuration properties setup in the `commonPipelineEnvironment` object.
## Side effects
none
## Exceptions
none
## Example
```groovy
def propertiesMap = commonPipelineEnvironment.getConfigProperties()
```

View File

@@ -0,0 +1,30 @@
# getConfigProperty
## Description
Gets a specific value from the configuration properties stored in the `commonPipelineEnvironment` object.
## Parameters
| parameter | mandatory | default | possible values |
| -----------|-----------|---------|-----------------|
| `property` | yes | | |
* `property` - the specific property to be retrieved from the configuration properties stored in the `commonPipelineEnvironment` object.
## Return values
The value of the property in the configuration properties stored in the `commonPipelineEnvironment` object.
## Side effects
none
## Exceptions
none
## Example
```groovy
def deployHost = commonPipelineEnvironment.getConfigProperty('DEPLOY_HOST')
```

View File

@@ -0,0 +1,26 @@
# getMtarFileName
## Description
Gets the file name of the mtar archive. The mtar archive is created in the [mtaBuild](../../steps/mtaBuild) step.
## Parameters
none
## Return values
The mtar archive file name stored in the `commonPipelineEnvironment` object.
## Side effects
none
## Exceptions
none
## Example
```groovy
def mtarFileName = commonPipelineEnvironment.getMtarFileName()
```

View File

@@ -0,0 +1,33 @@
# setConfigProperties
## Description
Sets the map of configuration properties stored in the `commonPipelineEnvironment` object.
Any existing `configProperties` map is overwritten.
## Parameters
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
| `map` | yes | | |
* `map` - the map of configuration properties to set in the `commonPipelineEnvironment` object.
## Return values
none
## Side effects
none
## Exceptions
none
## Example
```groovy
def map = [DEPLOY_HOST: 'deploy-host.com', DEPLOY_ACCOUNT: 'deploy-account']
commonPipelineEnvironment.setConfigProperties(map)
```

View File

@@ -0,0 +1,34 @@
# setConfigProperty
## Description
Sets a specific property of the configuration stored in the `commonPipelineEnvironment` object.
Any existing property is overwritten.
## Parameters
| parameter | mandatory | default | possible values |
| -----------|-----------|---------|-----------------|
| `property` | yes | | |
| `value` | yes | | |
* `property` - property key to set in the `commonPipelineEnvironment` object.
* `value`- the value to set the property to.
## Return values
none
## Side effects
none
## Exceptions
none
## Example
```groovy
commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'my-deploy-host.com')
```

View File

@@ -0,0 +1,31 @@
# setMtarFileName
## Description
Sets the file name of the mtar archive. The mtar archive is created in the [mtaBuild](../../steps/mtaBuild) step.
This does not change the file name of the actual mtar archive file.
## Parameters
| parameter | mandatory | default | possible values |
| ---------------|-----------|---------|-----------------|
| `mtarFileName` | yes | | |
* `mtarFileName` - the String to be set as value for `mtarFileName` in `commonPipelineEnvironment`.
## Return values
none
## Side effects
none
## Exceptions
none
## Example
```groovy
commonPipelineEnvironment.setMtarFileName('my.file.name.mtar')
```

View File

@@ -0,0 +1,63 @@
# handlePipelineStepErrors
## Description
Used by other steps to make error analysis easier. Lists parameters and other data available to the step in which the error occurs.
## Prerequisites
none
## Parameters
| parameter | mandatory | default | possible values |
| -----------------|-----------|---------|-----------------|
| `stepParameters` | yes | | |
| `stepName` | yes | | |
| `echoDetails` | yes | true | true, false |
* `stepParameters` - The parameters from the step to be executed. The list of parameters is then shown in the console output.
* `stepName` - The name of the step executed to be shown in the console output.
* `echoDetails` - if set to true will output the following as console output:
1. Step beginning: `--- BEGIN LIBRARY STEP: ${stepName}.groovy ---`
2. Step end: `--- END LIBRARY STEP: ${stepName}.groovy ---`
3. Step errors:
```
----------------------------------------------------------
--- ERROR OCCURED IN LIBRARY STEP: ${stepName}
----------------------------------------------------------
FOLLOWING PARAMETERS WERE AVAILABLE TO THIS STEP:
***
${stepParameters}
***
ERROR WAS:
***
${err}
***
FURTHER INFORMATION:
* Documentation of step ${stepName}: .../${stepName}/
* Pipeline documentation: https://...
* GitHub repository for pipeline steps: https://...
----------------------------------------------------------
```
## Return value
none
## Side effects
none
## Exceptions
none
## Example
```groovy
handlePipelineStepErrors (stepName: 'executeHealthCheck', stepParameters: parameters) {
def url = new Utils().getMandatoryParameter(parameters, 'url', null)
def statusCode = curl(url)
if (statusCode != '200')
error "Health Check failed: ${statusCode}"
}
```

View File

@@ -0,0 +1,45 @@
# mtaBuild
## Description
Executes the SAP MTA Archive Builder to create an mtar archive of the application.
## Prerequisites
* **SAP MTA Archive Builder** - available for download on the SAP Marketplace.
* **Java 8 or higher** - necessary to run the `mta.jar` file.
* **NodeJS installed** - the MTA Builder uses `npm` to download node module dependencies such as `grunt`.
## Parameters
| parameter | mandatory | default | possible values |
| -----------------|-----------|-----------------------------------|--------------------|
| `script` | yes | | |
| `buildTarget` | yes | | 'CF', 'NEO', 'XSA' |
| `mtaJarLocation` | no | | |
* `script` The common script environment of the Jenkinsfile running. Typically the reference to the script calling the pipeline step is provided with the `this` parameter, as in `script: this`. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for retrieving, for example, configuration parameters.
* `buildTarget` The target platform to which the mtar can be deployed.
* `mtaJarLocation` The path of the `mta.jar` file. If no parameter is provided, the path is retrieved from the Jenkins environment variables using `env.MTA_JAR_LOCATION`. If the Jenkins environment variable is not set it is assumed that `mta.jar` is located in the current working directory.
## Return value
The file name of the resulting archive is returned with this step. The file name is extracted from the key `ID` defined in `mta.yaml`.
## Side effects
1. The file name of the resulting archive is written to the `commonPipelineEnvironment` with variable name `mtarFileName`.
2. As version number the timestamp is written into the `mta.yaml` file, that is packaged into the built archive.
## Exceptions
* `AbortException`
* If there is an invalid `buildTarget`.
* If there is no key `ID` inside the `mta.yaml` file.
## Example
```groovy
def mtarFileName
dir('/path/to/FioriApp'){
mtarFileName = mtaBuild script:this, buildTarget: 'NEO'
}
```

View File

@@ -0,0 +1,61 @@
# neoDeploy
## Description
Deploys an Application to SAP Cloud Platform (SAP CP) using the SAP Cloud Platform Console Client (Neo Java Web SDK).
## Prerequisites
* **SAP CP account** - the account to where the application is deployed.
* **SAP CP user for deployment** - a user with deployment permissions in the given account.
* **Jenkins credentials for deployment** - must be configured in Jenkins credentials with a dedicated Id.
![Jenkins credentials configuration](../images/neo_credentials.png)
* **Neo Java Web SDK** - can be downloaded from [Maven Central](http://central.maven.org/maven2/com/sap/cloud/neo-java-web-sdk/). The Neo Java Web SDK
needs to be extracted into the folder provided by `neoHome`. In case this parameters is not provided and there is no NEO_HOME parameter in the environment
`<neoRoot>/tools` needs to be in the `PATH`.
* **Java 8 or higher** - needed by the *Neo-Java-Web-SDK*
## Parameters
| parameter | mandatory | default | possible values |
| -------------------|-----------|------------------------------------------------------------------------------------------|-----------------|
| `script` | yes | | |
| `archivePath` | yes | | |
| `deployHost` | no | `'DEPLOY_HOST'` from `commonPipelineEnvironment` | |
| `deployAccount` | no | `'CI_DEPLOY_ACCOUNT'` from `commonPipelineEnvironment` | |
| `neoCredentialsId` | no | `'CI_CREDENTIALS_ID'` | |
| `neoHome` | no | Environment is checked for `NEO_HOME`, otherwise the neo toolset is expected in the path | |
* `script` The common script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for retrieving e.g. configuration parameters.
* `archivePath` The path to the archive for deployment to SAP CP.
* `deployHost` The SAP Cloud Platform host to deploy to.
* `deployAccount` The SAP Cloud Platform account to deploy to.
* `credentialsId` The Jenkins credentials containing user and password used for SAP CP deployment.
* `neoHome` The path to the `neo-java-web-sdk` tool used for SAP CP deployment. If no parameter is provided, the path is retrieved from the Jenkins environment variables using `env.NEO_HOME`. If this Jenkins environment variable is not set it is assumed that the tool is available in the `PATH`.
## Return value
none
## Side effects
none
## Exceptions
* `Exception`
* If `archivePath` is not provided.
* `AbortException
* If neo-java-web-sdk is not installed, or `neoHome`is wrong.
* If `deployHost` is wrong.
* If `deployAccount` is wrong.
* `CredentialNotFoundException`
* If the credentials cannot be resolved.
## Example
```groovy
neoDeploy script: this, archivePath: 'path/to/archiveFile.mtar', credentialsId: 'my-credentials-id'
```

View File

@@ -0,0 +1,40 @@
# setupCommonPipelineEnvironment
## Description
Initializes the [`commonPipelineEnvironment`](commonPipelineEnvironment.md), which is used throughout the complete pipeline.
!!! tip
This step needs to run at the beginning of a pipeline right after the SCM checkout.
Then subsequent pipeline steps consume the information from `commonPipelineEnvironment`; it does not need to be passed to pipeline steps explicitly.
## Prerequisites
* A **configuration file** with properties (default location: `.pipeline/config.properties`). The property values are used as default values in many pipeline steps.
## Parameters
| parameter | mandatory | default | possible values |
| ------------ |-----------|-------------------------------|-----------------|
| `script` | yes | - | |
| `configFile` | no | `.pipeline/config.properties` | |
* `script` - The reference to the pipeline script (Jenkinsfile). Normally `this` needs to be provided.
* `configFile` - Property file defining project specific settings.
## Return value
none
## Side effects
none
## Exceptions
none
## Example
```groovy
setupCommonPipelineEnvironment script: this
```

View File

@@ -0,0 +1,33 @@
# toolValidate
## Description
Checks the existence and compatibility of a tool, necessary for a successful pipeline execution.
In case a violation is found, an exception is raised.
## Parameters
| parameter | mandatory | default | possible values |
| -----------------|-----------|-----------------------------------|----------------------------|
| `tool` | yes | | 'java', 'mta', 'neo' |
| `home` | yes | | |
* `tool` The tool that is checked for existence and compatible version.
* `home` The location in the file system where Jenkins can access the tool.
## Exceptions
* `IllegalArgumentException`
* If at least one of the parameters `tool`, `home` is not provided.
* `AbortException`
* If `tool` is not supported.
## Example
```groovy
toolValidate tool: 'neo', home:'/path/to/neo-java-web-sdk'
```

38
documentation/mkdocs.yml Normal file
View File

@@ -0,0 +1,38 @@
site_name: Jenkins 2.0 Pipelines
pages:
- Home: index.md
- 'Library steps':
- commonPipelineEnvironment: steps/commonPipelineEnvironment.md
- handlePipelineStepErrors: steps/handlePipelineStepErrors.md
- toolValidate: steps/toolValidate.md
- mtaBuild: steps/mtaBuild.md
- neoDeploy: steps/neoDeploy.md
- setupCommonPipelineEnvironment: steps/setupCommonPipelineEnvironment.md
- 'Library scripts':
- FileUtils: scripts/fileUtils.md
- Utils: scripts/utils.md
- Version: scripts/version.md
- 'Required Plugins': jenkins/requiredPlugins.md
theme: 'material'
extra:
logo: 'images/piper_head_white.png'
site_favicon: 'images/favicon.ico'
palette:
primary: 'teal'
accent: 'purple'
font:
text: 'Slabo 13px'
code: 'Ubuntu Mono'
markdown_extensions:
- admonition
- codehilite(guess_lang=false)
- toc(permalink=true)
- footnotes
- pymdownx.superfences
extra_css:
- 'css/extra.css'
edit_uri: edit/master/documentation/docs
docs_dir: docs
site_dir: docs-gen
repo_url: https://github.com/SAP/jenkins-library

252
pom.xml Normal file
View File

@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>2.21</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.sap.cp.jenkins</groupId>
<artifactId>pipeline-library</artifactId>
<version>0.0.1</version>
<name>SAP CP Piper Library</name>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
<properties>
<jenkins.version>2.32.3</jenkins.version>
<pipeline.version>2.5</pipeline.version>
<cps.global.lib.version>2.6</cps.global.lib.version>
<workflow-cps-plugin.version>2.28</workflow-cps-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-aggregator</artifactId>
<version>${pipeline.version}</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps-global-lib</artifactId>
<version>${cps.global.lib.version}</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>http_request</artifactId>
<version>1.8.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jenkins-ci.plugins.workflow/workflow-api -->
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-api</artifactId>
<version>2.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jenkins-ci.plugins.workflow/workflow-support -->
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>2.13</version>
</dependency>
<!-- plugins from https://github.com/jenkinsci/workflow-cps-global-lib-plugin/blob/master/pom.xml -->
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<version>${workflow-cps-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git-server</artifactId>
<version>1.5</version>
<exclusions>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git-client</artifactId>
<version>1.19.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>cloudbees-folder</artifactId>
<version>5.12</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-scm-step</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.apache.ivy</groupId>
<artifactId>ivy</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jenkins-ci.plugins/ssh-agent -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ssh-agent</artifactId>
<version>1.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<version>2.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-basic-steps</artifactId>
<version>2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-durable-task-step</artifactId>
<version>2.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git</artifactId>
<version>3.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>performance</artifactId>
<version>2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<version>2.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>pipeline-utility-steps</artifactId>
<version>1.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>subversion</artifactId>
<version>2.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit-cli</artifactId>
<version>1.8.14</version>
<scope>test</scope>
</dependency>
<!--LesFurets Jenkins Pipeline Unit testing framework: https://github.com/lesfurets/JenkinsPipelineUnit-->
<dependency>
<groupId>com.lesfurets</groupId>
<artifactId>jenkins-pipeline-unit</artifactId>
<version>1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.12</version>
<executions>
<execution>
<id>add-test-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>test/java</source>
<source>test/groovy</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<testSourceDirectory>test/java</testSourceDirectory>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerId>groovy-eclipse-compiler</compilerId>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>2.9.2-01</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-batch</artifactId>
<version>2.4.3-01</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,21 @@
package com.sap.piper
import hudson.AbortException
import java.io.File
class FileUtils implements Serializable {
static validateDirectory(dir) {
if (!dir) throw new IllegalArgumentException("The parameter 'dir' can not be null or empty.")
def file = new File(dir)
if (!file.exists()) throw new AbortException("'${file.getAbsolutePath()}' does not exist.")
if (!file.isDirectory()) throw new AbortException("'${file.getAbsolutePath()}' is not a directory.")
}
static validateDirectoryIsNotEmpty(dir) {
validateDirectory(dir)
def file = new File(dir)
if (file.list().size() == 0) throw new AbortException("'${file.getAbsolutePath()}' is empty.")
}
}

View File

@@ -0,0 +1,37 @@
package com.sap.piper
import com.cloudbees.groovy.cps.NonCPS
@NonCPS
def getMandatoryParameter(Map map, paramName, defaultValue) {
def paramValue = map[paramName]
if (paramValue == null)
paramValue = defaultValue
if (paramValue == null)
throw new Exception("ERROR - NO VALUE AVAILABLE FOR ${paramName}")
return paramValue
}
def retrieveGitCoordinates(script){
def gitUrl = script.params.GIT_URL
def gitBranch = script.params.GIT_BRANCH
if(!gitUrl && !gitBranch) {
echo "[INFO] Parameters 'GIT_URL' and 'GIT_BRANCH' not set in Jenkins job configuration. Assuming application to be built is contained in the same repository as this Jenkinsfile."
gitUrl = scm.userRemoteConfigs[0].url
gitBranch = scm.branches[0].name
}
else if(!gitBranch) {
error "Parameter 'GIT_BRANCH' not set in Jenkins job configuration. Either set both GIT_URL and GIT_BRANCH of the application to be built as Jenkins job parameters or put this Jenkinsfile into the same repository as the application to be built."
}
else if(!gitUrl) {
error "Parameter 'GIT_URL' not set in Jenkins job configuration. Either set both GIT_URL and GIT_BRANCH of the application to be built as Jenkins job parameters or put this Jenkinsfile into the same repository as the application to be built."
}
echo "[INFO] Building '${gitBranch}@${gitUrl}'."
return [url: gitUrl, branch: gitBranch]
}

View File

@@ -0,0 +1,52 @@
package com.sap.piper
import hudson.AbortException
class Version implements Serializable {
final def major
final def minor
final def patch
Version(major, minor, patch = -1) {
if (major < 0) throw new IllegalArgumentException("The parameter 'major' can not have a value less than 0.")
if (minor < 0) throw new IllegalArgumentException("The parameter 'minor' can not have a value less than 0.")
this.major = major
this.minor = minor
this.patch = patch
}
Version(text) {
if (!text) throw new IllegalArgumentException("The parameter 'text' can not be null or empty.")
def group = text =~ /(\d+[.]\d+[.]\d+)/
if (!group) throw new AbortException("The version '$text' has an unexpected format. The expected format is <major.minor.patch>.")
def i = group[0].size()-1
def versionNumbers = group[0][i].split("\\.")
major = versionNumbers[0].toInteger()
minor = versionNumbers[1].toInteger()
patch = versionNumbers[2].toInteger()
}
@Override
boolean equals(version) {
if (!version) throw new IllegalArgumentException("The parameter 'version' can not be null.")
return major == version.major && minor == version.minor && patch == version.patch
}
def isHigher(version) {
if (!version) throw new IllegalArgumentException("The parameter 'version' can not be null.")
return major > version.major || major == version.major && ( minor > version.minor || minor == version.minor && patch > version.patch)
}
def isCompatibleVersion(version) {
if (!version) throw new IllegalArgumentException("The parameter 'version' can not be null.")
return this == version || isHigher(version) && major == version.major
}
@Override
String toString() {
return patch != -1 ? "$major.$minor.$patch".toString() : "$major.$minor".toString()
}
}

21
template/step.groovy Normal file
View File

@@ -0,0 +1,21 @@
import com.cloudbees.groovy.cps.NonCPS
import com.sap.piper.Utils
/**
* Name of library step
*
* @param script global script environment of the Jenkinsfile run
* @param others document all parameters
*/
def call(Map parameters = [:], body) {
//ToDo: Change parameter stepName
handlePipelineStepErrors (stepName: 'stepName', stepParameters: parameters) {
def utils = new Utils()
def script = parameters.script
if (script == null)
script = [commonPipelineEnvironment: commonPipelineEnvironment]
//mandatory parameter - default cannot be null
def mandatoryPara = utils.getMandatoryParameter(parameters, 'paramName', 'param_default')
//optional parameter - default can be null
def param1 = parameters.get('param1Name')
}
}

33
template/step.md Normal file
View File

@@ -0,0 +1,33 @@
# <step name>
## Description
## Prerequisites
* **<prerequisite>** - further description.
## Parameters
| parameter | mandatory | default | possible values |
| ---------------|-----------|-----------------------------------|--------------------|
| | | | |
* detailed description of each parameter.
## Return value
none
## Side effects
none
## Exceptions
none
## Example
```groovy
```

View File

@@ -0,0 +1,93 @@
import org.junit.Rule
import org.junit.Before
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
import hudson.AbortException
import com.sap.piper.FileUtils
class FileUtilsTest {
@Rule
public ExpectedException thrown = new ExpectedException().none()
@Rule
public TemporaryFolder tmp = new TemporaryFolder()
private File emptyDir
private File notEmptyDir
private File notDir
@Before
void setup() {
emptyDir = tmp.newFolder('emptyDir')
notEmptyDir = tmp.newFolder('notEmptyDir')
File file = new File("${notEmptyDir.getAbsolutePath()}${File.separator}test.txt")
file.createNewFile()
notDir = tmp.newFile('noDir')
}
@Test
void nullValidateDirectoryTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'dir' can not be null or empty.")
FileUtils.validateDirectory(null)
}
@Test
void emptyValidateDirectoryTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'dir' can not be null or empty.")
FileUtils.validateDirectory('')
}
@Test
void doestNotExistValidateDirectoryTest() {
def path = "${emptyDir.getAbsolutePath()}${File.separator}test"
thrown.expect(AbortException)
thrown.expectMessage("'$path' does not exist.")
FileUtils.validateDirectory(path)
}
@Test
void isNotDirectoryValidateDirectoryTest() {
thrown.expect(AbortException)
thrown.expectMessage("'${notDir.getAbsolutePath()}' is not a directory.")
FileUtils.validateDirectory(notDir.getAbsolutePath())
}
@Test
void validateDirectoryTest() {
FileUtils.validateDirectory(notEmptyDir.getAbsolutePath())
}
@Test
void emptyDirValidateDirectoryIsNotEmptyTest() {
thrown.expect(AbortException)
thrown.expectMessage("'${emptyDir.getAbsolutePath()}' is empty.")
FileUtils.validateDirectoryIsNotEmpty(emptyDir.getAbsolutePath())
}
@Test
void validateDirectoryIsNotEmptyTest() {
FileUtils.validateDirectoryIsNotEmpty(notEmptyDir.getAbsolutePath())
}
}

View File

@@ -0,0 +1,368 @@
import hudson.AbortException
import org.jenkinsci.plugins.pipeline.utility.steps.shaded.org.yaml.snakeyaml.Yaml
import org.jenkinsci.plugins.pipeline.utility.steps.shaded.org.yaml.snakeyaml.parser.ParserException
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import com.lesfurets.jenkins.unit.BasePipelineTest
import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
import static com.lesfurets.jenkins.unit.global.lib.LibraryConfiguration.library
import static ProjectSource.projectSource
public class MTABuildTest extends BasePipelineTest {
@Rule
public ExpectedException thrown = new ExpectedException()
@Rule
public TemporaryFolder tmp = new TemporaryFolder()
def shellCalls = []
def echoes = []
def currentDir
def otherDir
def pipeline
def mtaBuildShEnv
@Before
public void setup(){
super.setUp()
currentDir = tmp.newFolder().toURI().getPath()[0..-2] //omit final '/'
otherDir = tmp.newFolder().toURI().getPath()[0..-2] //omit final '/'
pipeline = "${tmp.newFolder("pipeline").toURI().getPath()}pipeline"
def piperLib = library()
.name('piper-library-os')
.retriever(projectSource())
.targetPath('clonePath/is/not/necessary')
.defaultVersion('<irrelevant>')
.allowOverride(true)
.implicit(false)
.build()
helper.registerSharedLibrary(piperLib)
helper.registerAllowedMethod('sh', [String], {s -> shellCalls.add(s)} )
helper.registerAllowedMethod('echo', [String], {s -> echoes.add(s)} )
helper.registerAllowedMethod('readYaml', [Map], {
m ->
return new Yaml().load((m.file as File).text)
})
helper.registerAllowedMethod("dir", [String, Closure], {
s, c ->
currentDir = "${currentDir}/${s}"
c()
})
helper.registerAllowedMethod('pwd', [], { currentDir } )
helper.registerAllowedMethod("withEnv", [List.class, Closure.class],
{ l, c ->
mtaBuildShEnv = l
c()
})
helper.registerAllowedMethod('error', [String], { s -> throw new hudson.AbortException(s) })
binding.setVariable('PATH', '/usr/bin')
binding.setVariable('JAVA_HOME', '/opt/java')
binding.setVariable('env', [:])
}
@Test
public void straightForwardTest(){
binding.getVariable('env')['MTA_JAR_LOCATION'] = '/opt/mta'
new File("${currentDir}/mta.yaml") << defaultMtaYaml()
defaultPipeline()
def script = loadScript(pipeline)
def mtarFilePath = script.execute()
assert shellCalls[0].startsWith('sed -ie \"s/\\\${timestamp}/`date +%Y%m%d%H%M%S`/g\" ')
assert shellCalls[0].endsWith('/mta.yaml')
assert shellCalls[1].contains("PATH=./node_modules/.bin:/usr/bin")
assert shellCalls[1].contains(' -jar /opt/mta/mta.jar --mtar ')
assert mtarFilePath == "${currentDir}/com.mycompany.northwind.mtar"
assert echoes[1] == "[mtaBuild] MTA JAR \"/opt/mta/mta.jar\" retrieved from environment."
}
@Test
public void mtarFilePathFromCommonPipelineEnviromentTest(){
binding.getVariable('env')['MTA_JAR_LOCATION'] = '/opt/mta'
new File("${currentDir}/mta.yaml") << defaultMtaYaml()
returnMtarFilePathFromCommonPipelineEnvironmentPipeline()
def script = loadScript(pipeline)
def mtarFilePath = script.execute()
assert shellCalls[0].startsWith('sed -ie \"s/\\\${timestamp}/`date +%Y%m%d%H%M%S`/g\" ')
assert shellCalls[0].endsWith('/mta.yaml')
assert shellCalls[1].contains("PATH=./node_modules/.bin:/usr/bin")
assert shellCalls[1].contains(' -jar /opt/mta/mta.jar --mtar ')
assert mtarFilePath == "${currentDir}/com.mycompany.northwind.mtar"
assert echoes[1] == "[mtaBuild] MTA JAR \"/opt/mta/mta.jar\" retrieved from environment."
}
@Test
public void mtaBuildWithSurroundingDirTest(){
binding.getVariable('env')['MTA_JAR_LOCATION'] = '/opt/mta'
def newDirName = 'newDir'
new File("${currentDir}/${newDirName}").mkdirs()
new File("${currentDir}/${newDirName}/mta.yaml") << defaultMtaYaml()
withSurroundingDirPipeline()
def script = loadScript(pipeline)
def mtarFilePath = script.execute(newDirName)
assert shellCalls[0].startsWith('sed -ie \"s/\\\${timestamp}/`date +%Y%m%d%H%M%S`/g\" ')
assert shellCalls[0].endsWith('/mta.yaml')
assert shellCalls[1].contains("PATH=./node_modules/.bin:/usr/bin")
assert shellCalls[1].contains(' -jar /opt/mta/mta.jar --mtar ')
assert mtarFilePath == "${currentDir}/com.mycompany.northwind.mtar"
assert echoes[1] == "[mtaBuild] MTA JAR \"/opt/mta/mta.jar\" retrieved from environment."
}
@Test
void mtaHomeNotSetTest() {
new File("${currentDir}/mta.yaml") << defaultMtaYaml()
defaultPipeline()
def script = loadScript(pipeline)
def mtarFilePath = script.execute()
assert shellCalls[0].startsWith('sed -ie \"s/\\\${timestamp}/`date +%Y%m%d%H%M%S`/g\" ')
assert shellCalls[0].endsWith('/mta.yaml')
assert shellCalls[1].contains("PATH=./node_modules/.bin:/usr/bin")
assert shellCalls[1].contains(' -jar mta.jar --mtar ')
assert mtarFilePath == "${currentDir}/com.mycompany.northwind.mtar"
assert echoes[1] == "[mtaBuild] Using MTA JAR from current working directory."
}
@Test
void mtaHomeAsParameterTest() {
new File("${currentDir}/mta.yaml") << defaultMtaYaml()
mtaJarLocationAsParameterPipeline()
def script = loadScript(pipeline)
def mtarFilePath = script.execute()
assert shellCalls[0].startsWith('sed -ie \"s/\\\${timestamp}/`date +%Y%m%d%H%M%S`/g\" ')
assert shellCalls[0].endsWith('/mta.yaml')
assert shellCalls[1].contains("PATH=./node_modules/.bin:/usr/bin")
assert shellCalls[1].contains(' -jar /etc/mta/mta.jar --mtar ')
assert mtarFilePath == "${currentDir}/com.mycompany.northwind.mtar"
assert echoes[1] == "[mtaBuild] MTA JAR \"/etc/mta/mta.jar\" retrieved from parameters."
}
@Test
public void noMtaPresentTest(){
thrown.expect(FileNotFoundException)
defaultPipeline()
def script = loadScript(pipeline)
script.execute()
}
@Test
public void badMtaTest(){
thrown.expect(ParserException)
thrown.expectMessage('while parsing a block mapping')
new File("${currentDir}/mta.yaml") << badMtaYaml()
defaultPipeline()
def script = loadScript(pipeline)
script.execute()
}
@Test
public void noIdInMtaTest(){
thrown.expect(AbortException)
thrown.expectMessage("Property 'ID' not found in mta.yaml file at: '")
new File("${currentDir}/mta.yaml") << noIdMtaYaml()
defaultPipeline()
def script = loadScript(pipeline)
script.execute()
}
@Test
public void noBuildTargetTest(){
thrown.expect(Exception)
thrown.expectMessage("ERROR - NO VALUE AVAILABLE FOR buildTarget")
new File("${currentDir}/mta.yaml") << defaultMtaYaml()
noBuildTargetPipeline()
def script = loadScript(pipeline)
script.execute()
}
private defaultPipeline(){
new File(pipeline) << '''
@Library('piper-library-os')
execute(){
mtaBuild buildTarget: 'NEO'
}
return this
'''
}
private returnMtarFilePathFromCommonPipelineEnvironmentPipeline(){
new File(pipeline) << '''
@Library('piper-library-os')
execute(){
mtaBuild buildTarget: 'NEO'
return commonPipelineEnvironment.getMtarFilePath()
}
return this
'''
}
private mtaJarLocationAsParameterPipeline(){
new File(pipeline) << '''
@Library('piper-library-os')
execute(){
mtaBuild mtaJarLocation: '/etc/mta', buildTarget: 'NEO'
}
return this
'''
}
private withSurroundingDirPipeline(){
new File(pipeline) << '''
@Library('piper-library-os')
execute(dirPath){
dir("${dirPath}"){
mtaBuild buildTarget: 'NEO'
}
}
return this
'''
}
private noBuildTargetPipeline(){
new File(pipeline) << '''
@Library('piper-library-os')
execute(){
mtaBuild()
}
return this
'''
}
private defaultMtaYaml(){
return '''
_schema-version: "2.0.0"
ID: "com.mycompany.northwind"
version: 1.0.0
parameters:
hcp-deployer-version: "1.0.0"
modules:
- name: "fiorinorthwind"
type: html5
path: .
parameters:
version: 1.0.0-${timestamp}
build-parameters:
builder: grunt
build-result: dist
'''
}
private badMtaYaml(){
return '''
_schema-version: "2.0.0
ID: "com.mycompany.northwind"
version: 1.0.0
parameters:
hcp-deployer-version: "1.0.0"
modules:
- name: "fiorinorthwind"
type: html5
path: .
parameters:
version: 1.0.0-${timestamp}
build-parameters:
builder: grunt
build-result: dist
'''
}
private noIdMtaYaml(){
return '''
_schema-version: "2.0.0"
version: 1.0.0
parameters:
hcp-deployer-version: "1.0.0"
modules:
- name: "fiorinorthwind"
type: html5
path: .
parameters:
version: 1.0.0-${timestamp}
build-parameters:
builder: grunt
build-result: dist
'''
}
}

View File

@@ -0,0 +1,309 @@
import hudson.AbortException
import org.junit.rules.TemporaryFolder
import static com.lesfurets.jenkins.unit.global.lib.LibraryConfiguration.library
import static ProjectSource.projectSource
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import com.lesfurets.jenkins.unit.BasePipelineTest
class NeoDeploymentTest extends BasePipelineTest {
@Rule
public ExpectedException thrown = new ExpectedException().none()
@Rule
public TemporaryFolder tmp = new TemporaryFolder()
def script
def shellCalls = []
def pipeline
def echoes = []
def archivePath
@Before
void setup() {
super.setUp()
archivePath = "${tmp.newFolder("workspace").toURI().getPath()}archiveName.mtar"
pipeline = "${tmp.newFolder("pipeline").toURI().getPath()}pipeline"
def piperLib = library()
.name('piper-library-os')
.retriever(projectSource())
.targetPath('clonePath/is/not/necessary')
.defaultVersion('irrelevant')
.allowOverride(true)
.implicit(false)
.build()
helper.registerSharedLibrary(piperLib)
helper.registerAllowedMethod('sh', [String], { GString s ->
shellCalls.add(s.replaceAll(/\s+/, " ").trim())
})
helper.registerAllowedMethod('error', [String], { s -> throw new AbortException(s) })
helper.registerAllowedMethod('echo', [String], { s -> echoes.add(s) })
helper.registerAllowedMethod('usernamePassword', [Map], { m -> return m })
helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c ->
if(l[0].credentialsId == 'myCredentialsId') {
binding.setProperty('username', 'anonymous')
binding.setProperty('password', '********')
} else if(l[0].credentialsId == 'CI_CREDENTIALS_ID') {
binding.setProperty('username', 'defaultUser')
binding.setProperty('password', '********')
}
try {
c()
} finally {
binding.setProperty('username', null)
binding.setProperty('password', null)
}
})
binding.setVariable('env', [:])
}
@Test
void straightForwardTest() {
defaultPipeline()
binding.getVariable('env')['NEO_HOME'] = '/opt/neo'
new File(archivePath) << "dummy archive"
script = loadScript(pipeline)
script.execute(archivePath, 'myCredentialsId')
assert shellCalls[0] =~ /#!\/bin\/bash \/opt\/neo\/tools\/neo\.sh deploy-mta --user anonymous --host test\.deploy\.host\.com --source .* --account trialuser123 --password \*\*\*\*\*\*\*\* --synchronous/
assert echoes[1] == "[neoDeploy] Neo executable \"/opt/neo/tools/neo.sh\" retrieved from environment."
}
@Test
void badCredentialsIdTest() {
defaultPipeline()
binding.getVariable('env')['NEO_HOME'] = '/opt/neo'
new File(archivePath) << "dummy archive"
thrown.expect(MissingPropertyException)
script = loadScript(pipeline)
script.execute(archivePath, 'badCredentialsId')
}
@Test
void credentialsIdNotProvidedTest() {
noCredentialsIdPipeline()
binding.getVariable('env')['NEO_HOME'] = '/opt/neo'
new File(archivePath) << "dummy archive"
script = loadScript(pipeline)
script.execute(archivePath)
assert shellCalls[0] =~ /#!\/bin\/bash \/opt\/neo\/tools\/neo\.sh deploy-mta --user defaultUser --host test\.deploy\.host\.com --source .* --account trialuser123 --password \*\*\*\*\*\*\*\* --synchronous/
assert echoes[1] == "[neoDeploy] Neo executable \"/opt/neo/tools/neo.sh\" retrieved from environment."
}
@Test
void neoHomeNotSetTest() {
noCredentialsIdPipeline()
new File(archivePath) << "dummy archive"
script = loadScript(pipeline)
script.execute(archivePath)
assert shellCalls[0] =~ /#!\/bin\/bash neo deploy-mta --user defaultUser --host test\.deploy\.host\.com --source .* --account trialuser123 --password \*\*\*\*\*\*\*\* --synchronous/
assert echoes[1] == "Using Neo executable from PATH."
}
@Test
void neoHomeAsParameterTest() {
neoHomeParameterPipeline()
new File(archivePath) << "dummy archive"
script = loadScript(pipeline)
script.execute(archivePath, 'myCredentialsId')
assert shellCalls[0] =~ /#!\/bin\/bash \/etc\/neo\/tools\/neo\.sh deploy-mta --user anonymous --host test\.deploy\.host\.com --source .* --account trialuser123 --password \*\*\*\*\*\*\*\* --synchronous/
assert echoes[1] == "[neoDeploy] Neo executable \"/etc/neo/tools/neo.sh\" retrieved from parameters."
}
@Test
void archiveNotProvidedTest() {
noArchivePathPipeline()
thrown.expect(Exception)
thrown.expectMessage('ERROR - NO VALUE AVAILABLE FOR archivePath')
script = loadScript(pipeline)
script.execute()
}
@Test
void wrongArchivePathProvidedTest() {
defaultPipeline()
thrown.expect(AbortException)
thrown.expectMessage("Archive cannot be found with parameter archivePath: '")
script = loadScript(pipeline)
script.execute(archivePath, 'myCredentialsId')
}
@Test
void scriptNotProvidedTest() {
noScriptPipeline()
new File(archivePath) << "dummy archive"
thrown.expect(Exception)
thrown.expectMessage('ERROR - NO VALUE AVAILABLE FOR deployHost')
script = loadScript(pipeline)
script.execute(archivePath)
}
private defaultPipeline(){
new File(pipeline) << """
@Library('piper-library-os')
execute(archivePath, neoCredentialsId) {
commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'test.deploy.host.com')
commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'trialuser123')
node() {
neoDeploy script: this, archivePath: archivePath, neoCredentialsId: neoCredentialsId
}
}
return this
"""
}
private noCredentialsIdPipeline(){
new File(pipeline) << """
@Library('piper-library-os')
execute(archivePath) {
commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'test.deploy.host.com')
commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'trialuser123')
node() {
neoDeploy script: this, archivePath: archivePath
}
}
return this
"""
}
private neoHomeParameterPipeline(){
new File(pipeline) << """
@Library('piper-library-os')
execute(archivePath, neoCredentialsId) {
commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'test.deploy.host.com')
commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'trialuser123')
node() {
neoDeploy script: this, archivePath: archivePath, neoCredentialsId: neoCredentialsId, neoHome: '/etc/neo'
}
}
return this
"""
}
private noArchivePathPipeline(){
new File(pipeline) << """
@Library('piper-library-os')
execute() {
commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'test.deploy.host.com')
commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'trialuser123')
node() {
neoDeploy script: this
}
}
return this
"""
}
private noScriptPipeline(){
new File(pipeline) << """
@Library('piper-library-os')
execute(archivePath) {
node() {
neoDeploy archivePath: archivePath
}
}
return this
"""
}
}

View File

@@ -0,0 +1,39 @@
import com.lesfurets.jenkins.unit.global.lib.SourceRetriever
import groovy.transform.CompileStatic
import groovy.transform.Immutable
/**
* Retrieves the shared lib sources of the current project which are expected to be
* at the default location &quot;./vars&quot;.
*/
@Immutable
@CompileStatic
class ProjectSource implements SourceRetriever {
String sourceURL
/*
* None of the parameters provided in the signature are used in the use-case of that retriever.
*/
@Override
List<URL> retrieve(String repository, String branch, String targetPath) {
def sourceDir = new File(sourceURL)
if (sourceDir.exists()) {
return [sourceDir.getAbsoluteFile().toURI().toURL()]
}
throw new IllegalStateException("Directory $sourceDir.path does not exists")
}
static ProjectSource projectSource(String sourceDir = '.') {
new ProjectSource(sourceDir)
}
@Override
String toString() {
return "${getClass().getSimpleName()}{" +
"sourceURL='" + sourceURL + '\'' +
'}'
}
}

View File

@@ -0,0 +1,326 @@
import static com.lesfurets.jenkins.unit.global.lib.LibraryConfiguration.library
import static ProjectSource.projectSource
import org.apache.commons.exec.*
import hudson.AbortException
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
import com.lesfurets.jenkins.unit.BasePipelineTest
class ToolValidateTest extends BasePipelineTest {
@Rule
public ExpectedException thrown = new ExpectedException().none()
@Rule
public TemporaryFolder tmp = new TemporaryFolder()
private notEmptyDir
private messages = []
private script
@Before
void setup() {
super.setUp()
def piperLib = library()
.name('piper-library-os')
.retriever(projectSource())
.targetPath('clonePath/is/not/necessary')
.defaultVersion('irrelevant')
.allowOverride(true)
.implicit(false)
.build()
helper.registerSharedLibrary(piperLib)
helper.registerAllowedMethod('echo', [String], {s -> messages.add(s)})
def pipelinePath = "${tmp.newFolder("pipeline").toURI().getPath()}pipeline"
createPipeline(pipelinePath)
script = loadScript(pipelinePath)
notEmptyDir = tmp.newFolder('notEmptyDir')
def path = "${notEmptyDir.getAbsolutePath()}${File.separator}test.txt"
File file = new File(path)
file.createNewFile()
binding.setVariable('JAVA_HOME', notEmptyDir.getAbsolutePath())
binding.setVariable('home', notEmptyDir.getAbsolutePath())
}
@Test
void nullHomeTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'home' can not be null or empty.")
binding.setVariable('tool', 'java')
binding.setVariable('home', null)
script.execute()
}
@Test
void emptyHomeTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'home' can not be null or empty.")
binding.setVariable('tool', 'java')
binding.setVariable('home', '')
script.execute()
}
@Test
void nullToolTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'tool' can not be null or empty.")
binding.setVariable('tool', null)
script.execute()
}
@Test
void emptyToolTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'tool' can not be null or empty.")
binding.setVariable('tool', '')
script.execute()
}
@Test
void invalidToolTest() {
thrown.expect(AbortException)
thrown.expectMessage("The tool 'test' is not supported.")
binding.setVariable('tool', 'test')
script.execute()
}
@Test
void unableToValidateJavaTest() {
thrown.expect(AbortException)
thrown.expectMessage('The validation of Java failed.')
helper.registerAllowedMethod('sh', [Map], { Map m -> getNoVersion(m) })
binding.setVariable('tool', 'java')
script.execute()
}
@Test
void unableToValidateMtaTest() {
thrown.expect(AbortException)
thrown.expectMessage('The validation of SAP Multitarget Application Archive Builder failed.')
helper.registerAllowedMethod('sh', [Map], { Map m -> getNoVersion(m) })
binding.setVariable('tool', 'mta')
script.execute()
}
@Test
void unableToValidateNeoTest() {
thrown.expect(AbortException)
thrown.expectMessage('The validation of SAP Cloud Platform Console Client failed.')
helper.registerAllowedMethod('sh', [Map], { Map m -> getNoVersion(m) })
binding.setVariable('tool', 'neo')
script.execute()
}
@Test
void unableToValidateCmTest() {
thrown.expect(AbortException)
thrown.expectMessage('The validation of Change Management Command Line Interface failed.')
helper.registerAllowedMethod('sh', [Map], { Map m -> getNoVersion(m) })
binding.setVariable('tool', 'cm')
script.execute()
}
@Test
void validateIncompatibleVersionJavaTest() {
thrown.expect(AbortException)
thrown.expectMessage('The installed version of Java is 1.7.0.')
helper.registerAllowedMethod('sh', [Map], { Map m -> getIncompatibleVersion(m) })
binding.setVariable('tool', 'java')
script.execute()
}
@Test
void validateIncompatibleVersionMtaTest() {
thrown.expect(AbortException)
thrown.expectMessage('The installed version of SAP Multitarget Application Archive Builder is 1.0.5.')
helper.registerAllowedMethod('sh', [Map], { Map m -> getIncompatibleVersion(m) })
binding.setVariable('tool', 'mta')
script.execute()
}
@Test
void validateNeoIncompatibleVersionTest() {
thrown.expect(AbortException)
thrown.expectMessage('The installed version of SAP Cloud Platform Console Client is 1.126.51.')
helper.registerAllowedMethod('sh', [Map], { Map m -> getIncompatibleVersion(m) })
binding.setVariable('tool', 'neo')
script.execute()
}
@Test
void validateCmIncompatibleVersionTest() {
thrown.expect(AbortException)
thrown.expectMessage('The installed version of Change Management Command Line Interface is 0.0.0.')
helper.registerAllowedMethod('sh', [Map], { Map m -> getIncompatibleVersion(m) })
binding.setVariable('tool', 'cm')
script.execute()
}
@Test
void validateJavaTest() {
helper.registerAllowedMethod('sh', [Map], { Map m -> getVersion(m) })
binding.setVariable('tool', 'java')
script.execute()
assert messages[0].contains('--- BEGIN LIBRARY STEP: toolValidate.groovy ---')
assert messages[1].contains('[INFO] Validating Java version 1.8.0 or compatible version.')
assert messages[2].contains('[INFO] Java version 1.8.0 is installed.')
assert messages[3].contains('--- END LIBRARY STEP: toolValidate.groovy ---')
}
@Test
void validateMtaTest() {
helper.registerAllowedMethod('sh', [Map], { Map m -> getVersion(m) })
binding.setVariable('tool', 'mta')
script.execute()
assert messages[0].contains('--- BEGIN LIBRARY STEP: toolValidate.groovy ---')
assert messages[1].contains('[INFO] Validating SAP Multitarget Application Archive Builder version 1.0.6 or compatible version.')
assert messages[2].contains('[INFO] SAP Multitarget Application Archive Builder version 1.0.6 is installed.')
assert messages[3].contains('--- END LIBRARY STEP: toolValidate.groovy ---')
}
@Test
void validateNeoTest() {
helper.registerAllowedMethod('sh', [Map], { Map m -> getVersion(m) })
binding.setVariable('tool', 'neo')
script.execute()
assert messages[0].contains('--- BEGIN LIBRARY STEP: toolValidate.groovy ---')
assert messages[1].contains('[INFO] Validating SAP Cloud Platform Console Client version 3.39.10 or compatible version.')
assert messages[2].contains('[INFO] SAP Cloud Platform Console Client version 3.39.10 is installed.')
assert messages[3].contains('--- END LIBRARY STEP: toolValidate.groovy ---')
}
@Test
void validateCmTest() {
helper.registerAllowedMethod('sh', [Map], { Map m -> getVersion(m) })
binding.setVariable('tool', 'cm')
script.execute()
assert messages[0].contains('--- BEGIN LIBRARY STEP: toolValidate.groovy ---')
assert messages[1].contains('[INFO] Validating Change Management Command Line Interface version 0.0.1 or compatible version.')
assert messages[2].contains('[INFO] Change Management Command Line Interface version 0.0.1 is installed.')
assert messages[3].contains('--- END LIBRARY STEP: toolValidate.groovy ---')
}
private createPipeline(pipelinePath){
new File(pipelinePath) << """
@Library('piper-library-os')
execute() {
node() {
toolValidate tool: tool, home: home
}
}
return this
"""
}
private getNoVersion(Map m) {
throw new AbortException('script returned exit code 127')
}
private getVersion(Map m) {
if(m.script.contains('java -version')) {
return '''openjdk version \"1.8.0_121\"
OpenJDK Runtime Environment (build 1.8.0_121-8u121-b13-1~bpo8+1-b13)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)'''
} else if(m.script.contains('mta.jar -v')) {
return '1.0.6'
} else if(m.script.contains('neo.sh version')) {
return '''SAP Cloud Platform Console Client
SDK version : 3.39.10
Runtime : neo-java-web'''
} else if(m.script.contains('cmclient -v')) {
return '0.0.1-beta-2 : fc9729964a6acf5c1cad9c6f9cd6469727625a8e'
}
}
private getIncompatibleVersion(Map m) {
if(m.script.contains('java -version')) {
return '''openjdk version \"1.7.0_121\"
OpenJDK Runtime Environment (build 1.7.0_121-8u121-b13-1~bpo8+1-b13)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)'''
} else if(m.script.contains('mta.jar -v')) {
return '1.0.5'
} else if(m.script.contains('neo.sh version')) {
return '''SAP Cloud Platform Console Client
SDK version : 1.126.51
Runtime : neo-java-web'''
} else if(m.script.contains('cmclient -v')) {
return '0.0.0-beta-1 : fc9729964a6acf5c1cad9c6f9cd6469727625a8e'
}
}
}

View File

@@ -0,0 +1,192 @@
import org.junit.Rule
import org.junit.Before
import org.junit.Test
import org.junit.rules.ExpectedException
import hudson.AbortException
import com.sap.piper.Version
class VersionTest {
@Rule
public ExpectedException thrown = new ExpectedException().none()
@Test
void illegalMajorVersionTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'major' can not have a value less than 0.")
Version version = new Version(-1,0)
}
@Test
void illegalMinorVersionTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'minor' can not have a value less than 0.")
Version version = new Version(0,-1)
}
@Test
void nullMajorVersionTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'major' can not have a value less than 0.")
Version version = new Version(null,0)
}
@Test
void nullMinorVersionTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'minor' can not have a value less than 0.")
Version version = new Version(0, null)
}
@Test
void nullVersionTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'text' can not be null or empty.")
Version version = new Version(null)
}
@Test
void emptyVersionTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'text' can not be null or empty.")
Version version = new Version('')
}
@Test
void unexpectedFormatTest() {
thrown.expect(AbortException)
thrown.expectMessage("The version '0-0.1' has an unexpected format. The expected format is <major.minor.patch>.")
Version version = new Version('0-0.1')
}
@Test
void isEqualNullTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'version' can not be null.")
Version version = new Version(0,0,1)
version.equals(null)
}
@Test
void isEqualPatchTest() {
Version version1 = new Version(0,0,1)
Version version2 = new Version('0.0.1')
assert version1.equals(version2)
}
@Test
void isEqualMinorTest() {
Version version1 = new Version(0,1,0)
Version version2 = new Version('0.1.0')
assert version1.equals(version2)
}
@Test
void isEqualMajorTest() {
Version version1 = new Version(1,0,0)
Version version2 = new Version('1.0.0')
assert version1.equals(version2)
}
@Test
void isHigherNullTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'version' can not be null.")
Version version = new Version(0,0,1)
version.isHigher(null)
}
@Test
void isHigherPatchTest() {
Version version1 = new Version(0,0,1)
Version version2 = new Version('0.0.2')
assert version2.isHigher(version1)
}
@Test
void isHigherMinorTest() {
Version version1 = new Version(0,1,0)
Version version2 = new Version('0.2.0')
assert version2.isHigher(version1)
}
@Test
void isHigherMajorTest() {
Version version1 = new Version(1,0,0)
Version version2 = new Version('2.0.0')
assert version2.isHigher(version1)
}
@Test
void isCompatibleVersionNullTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("The parameter 'version' can not be null.")
Version version = new Version(0,0,1)
version.isCompatibleVersion(null)
}
@Test
void isCompatibleVersionPatchTest() {
Version version1 = new Version(0,0,1)
Version version2 = new Version('0.0.2')
assert version2.isCompatibleVersion(version1)
}
@Test
void isCompatibleVersionMinorTest() {
Version version1 = new Version(0,1,0)
Version version2 = new Version('0.2.0')
assert version2.isCompatibleVersion(version1)
}
@Test
void isIncompatibleVersionTest() {
Version version1 = new Version(1,0,0)
Version version2 = new Version('2.0.0')
assert !version2.isCompatibleVersion(version1)
}
}

View File

@@ -0,0 +1,39 @@
import java.io.File;
import java.io.IOException;
import javax.inject.Inject;
import jenkins.model.Jenkins;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariableList;
import org.jenkinsci.plugins.workflow.cps.global.WorkflowLibRepository;
import org.junit.ClassRule;
import org.junit.Rule;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.RestartableJenkinsRule;
public class AbstractJenkinsTest {
@ClassRule
public static BuildWatcher buildWatcher = new BuildWatcher();
@Rule
public RestartableJenkinsRule story = new RestartableJenkinsRule();
@Inject
protected Jenkins jenkins;
@Inject
WorkflowLibRepository repo;
@Inject
protected UserDefinedGlobalVariableList uvl;
public AbstractJenkinsTest() {
super();
}
protected void copyLibrarySources() {
try {
FileUtils.copyDirectory(new File("vars"), new File(repo.workspace, "vars"));
FileUtils.copyDirectory(new File("src"), new File(repo.workspace, "src"));
FileUtils.copyDirectory(new File("resources"), new File(repo.workspace, "resources"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}

91
test/java/AcmeTest.java Normal file
View File

@@ -0,0 +1,91 @@
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariable;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.junit.Test;
import org.junit.runners.model.Statement;
import java.io.File;
import java.util.Arrays;
public class AcmeTest extends AbstractJenkinsTest {
/**
* Test acme getter and setter
*/
@Test
public void acmeTest() throws Exception {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
//File vars = new File(repo.workspace, UserDefinedGlobalVariable.PREFIX);
File vars = new File(repo.workspace, "vars");
vars.mkdirs();
FileUtils.writeStringToFile(new File(vars, "acme.groovy"), StringUtils.join(Arrays.asList(
"class acme implements Serializable {",
" private String name = 'initial'",
" def setName(value) {",
" this.name = value",
" }",
" def getName() {",
" this.name",
" }",
" def caution(message) {",
" echo \"Hello, ${name}! CAUTION: ${message}\"",
" }",
"}")
, "\n"));
// simulate the effect of push
uvl.rebuild();
WorkflowJob p = jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node {\n" +
"acme.setName('acmeName')\n"+
"echo acme.getName()\n" +
"}",
true));
// build this workflow
WorkflowRun b = story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
story.j.assertLogContains("acmeName", b);
}
});
}
//@Test
public void acmeTest2() throws Exception {
story.addStep(new Statement() {
@Override
public void evaluate() throws Throwable {
copyLibrarySources();
// simulate the effect of push
uvl.rebuild();
WorkflowJob p = jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"import com.sap.piper.Utils\n" +
"node {\n" +
"acme.setName('myName')\n"+
"assert acme.getName() == 'myName'\n" +
"}",
true));
// build this workflow
WorkflowRun b = story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
}
});
}
}

View File

@@ -0,0 +1,40 @@
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.junit.runners.model.Statement;
public class _TemplateTest extends AbstractJenkinsTest {
/**
* Test ... step
*/
//@Test
public void testWhatEver() throws Exception {
story.addStep(new Statement() {
@Override
public void evaluate() throws Throwable {
copyLibrarySources();
// simulate the effect of push
uvl.rebuild();
WorkflowJob p = jenkins.createProject(WorkflowJob.class, "p");
//copy test resources into workspace
FileUtils.copyDirectory(new File("test/resources"), new File(jenkins.getWorkspaceFor(p).getRemote(), "resources"));
p.setDefinition(new CpsFlowDefinition(
"node {\n" +
"\n" +
"\n" +
"}",
true)
);
// build this workflow
WorkflowRun b = story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
//story.j.assertLogContains("this is part of the log", b);
}
});
}
}

View File

@@ -0,0 +1,27 @@
class commonPipelineEnvironment implements Serializable {
private Map configProperties = [:]
private String mtarFilePath
def setConfigProperties(map) {
configProperties = map
}
def getConfigProperties() {
return configProperties
}
def setConfigProperty(property, value) {
configProperties[property] = value
}
def getConfigProperty(property) {
if (configProperties[property] != null)
return configProperties[property].trim()
else
return configProperties[property]
}
def getMtarFilePath() {
return mtarFilePath
}
void setMtarFilePath(mtarFilePath) {
this.mtarFilePath = mtarFilePath
}
}

View File

@@ -0,0 +1,44 @@
def call(Map parameters = [:], body) {
def stepParameters = parameters.stepParameters //mandatory
def stepName = parameters.stepName //mandatory
def echoDetails = parameters.get('echoDetails', true)
try {
if (stepParameters == null && stepName == null)
error "step handlePipelineStepErrors requires following mandatory parameters: stepParameters, stepName"
if (echoDetails)
echo "--- BEGIN LIBRARY STEP: ${stepName}.groovy ---"
body()
} catch (Throwable err) {
if (echoDetails)
echo """----------------------------------------------------------
--- ERROR OCCURED IN LIBRARY STEP: ${stepName}
----------------------------------------------------------
FOLLOWING PARAMETERS WERE AVAILABLE TO THIS STEP:
***
${stepParameters}
***
ERROR WAS:
***
${err}
***
FURTHER INFORMATION:
* Documentation of step ${stepName}: .../${stepName}/
* Pipeline documentation: https://...
* GitHub repository for pipeline steps: https://...
----------------------------------------------------------"""
throw err
} finally {
if (echoDetails)
echo "--- END LIBRARY STEP: ${stepName}.groovy ---"
}
}

63
vars/mtaBuild.groovy Normal file
View File

@@ -0,0 +1,63 @@
import com.sap.piper.Utils
/**
* mtaBuild
* Builds Fiori app with Multitarget Archiver
* Prerequisite: InitializeNpm needs to be called beforehand
*
*/
def call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: 'mtaBuild', stepParameters: parameters) {
def utils = new Utils()
def buildTarget = utils.getMandatoryParameter(parameters, 'buildTarget', null)
def script = parameters.script
if (script == null){
script = [commonPipelineEnvironment: commonPipelineEnvironment]
}
def mtaYaml = readYaml file: "${pwd()}/mta.yaml"
//[Q]: Why not yaml.dump()? [A]: This reformats the whole file.
sh "sed -ie \"s/\\\${timestamp}/`date +%Y%m%d%H%M%S`/g\" ${pwd()}/mta.yaml"
def id = mtaYaml.ID
if (!id) {
error "Property 'ID' not found in mta.yaml file at: '${pwd()}'"
}
def mtarFileName = "${id}.mtar"
def mtaJar = getMtaJar(parameters)
sh """#!/bin/bash
export PATH=./node_modules/.bin:${PATH}
java -jar ${mtaJar} --mtar ${mtarFileName} --build-target=${buildTarget} build
"""
def mtarFilePath = "${pwd()}/${mtarFileName}"
script.commonPipelineEnvironment.setMtarFilePath(mtarFilePath)
return mtarFilePath
}
}
private getMtaJar(parameters) {
def mtaJarLocation = 'mta.jar' //default, maybe it is in current working directory
if(parameters?.mtaJarLocation){
mtaJarLocation = "${parameters.mtaJarLocation}/mta.jar"
echo "[mtaBuild] MTA JAR \"${mtaJarLocation}\" retrieved from parameters."
return mtaJarLocation
}
if(env?.MTA_JAR_LOCATION){
mtaJarLocation = "${env.MTA_JAR_LOCATION}/mta.jar"
echo "[mtaBuild] MTA JAR \"${mtaJarLocation}\" retrieved from environment."
return mtaJarLocation
}
echo "[mtaBuild] Using MTA JAR from current working directory."
return mtaJarLocation
}

78
vars/neoDeploy.groovy Normal file
View File

@@ -0,0 +1,78 @@
/**
* neoDeployment
* Deploys file to Neo
* Prerequisite: Location of file to be deployed as 'archivePath' parameter
* Needs to be surrounded by withCredentials closure to provide username and password
*
* @param archivePath Path of the archive to be deployed.
*/
import com.sap.piper.Utils
def call(parameters = [:]) {
handlePipelineStepErrors (stepName: 'neoDeploy', stepParameters: parameters) {
def utils = new Utils()
def script = parameters.script
if (script == null){
script = [commonPipelineEnvironment: commonPipelineEnvironment]
}
def archivePath = new File(utils.getMandatoryParameter(parameters, 'archivePath', null))
if (!archivePath.isAbsolute()) {
archivePath = new File(pwd(), archivePath.getPath())
}
if (!archivePath.exists()){
error "Archive cannot be found with parameter archivePath: '${archivePath}'."
}
def defaultDeployHost = script.commonPipelineEnvironment.getConfigProperty('DEPLOY_HOST')
def defaultDeployAccount = script.commonPipelineEnvironment.getConfigProperty('CI_DEPLOY_ACCOUNT')
def defaultCredentialsId = script.commonPipelineEnvironment.getConfigProperty('neoCredentialsId')
if (defaultCredentialsId == null) {
defaultCredentialsId = 'CI_CREDENTIALS_ID'
}
def deployHost = utils.getMandatoryParameter(parameters, 'deployHost', defaultDeployHost)
def deployAccount = utils.getMandatoryParameter(parameters, 'deployAccount', defaultDeployAccount)
def credentialsId = parameters.get('neoCredentialsId', defaultCredentialsId)
def neoExecutable = getNeoExecutable(parameters)
withCredentials([usernamePassword(
credentialsId: credentialsId,
passwordVariable: 'password',
usernameVariable: 'username'
)]) {
sh """#!/bin/bash
${neoExecutable} deploy-mta \
--user ${username} \
--host ${deployHost} \
--source ${archivePath.getAbsolutePath()} \
--account ${deployAccount} \
--password ${password} \
--synchronous
"""
}
}
}
private getNeoExecutable(parameters) {
def neoExecutable = 'neo' // default, if nothing below applies maybe it is the path.
if (parameters?.neoHome) {
neoExecutable = "${parameters.neoHome}/tools/neo.sh"
echo "[neoDeploy] Neo executable \"${neoExecutable}\" retrieved from parameters."
return neoExecutable
}
if (env?.NEO_HOME) {
neoExecutable = "${env.NEO_HOME}/tools/neo.sh"
echo "[neoDeploy] Neo executable \"${neoExecutable}\" retrieved from environment."
return neoExecutable
}
echo "Using Neo executable from PATH."
return neoExecutable
}

View File

@@ -0,0 +1,14 @@
def call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: 'setupCommonPipelineEnvironment', stepParameters: parameters) {
def configFile = parameters.get('configFile', '.pipeline/config.properties')
def script = parameters.script
Map configMap = [:]
if (configFile.length() > 0)
configMap = readProperties (file: configFile)
script.commonPipelineEnvironment.setConfigProperties(configMap)
}
}

74
vars/toolValidate.groovy Normal file
View File

@@ -0,0 +1,74 @@
import com.sap.piper.FileUtils
import com.sap.piper.Version
import hudson.AbortException
/**
* toolValidate: validates if a tool is installed with the expected version.
*
* @param tool
* the tool to validate: java, mta, neo or cm.
* @param home
* the directory containing the tool.
*/
def call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: 'toolValidate', stepParameters: parameters) {
def tool = parameters.tool
def home = parameters.home
if (!tool) throw new IllegalArgumentException("The parameter 'tool' can not be null or empty.")
if (!home) throw new IllegalArgumentException("The parameter 'home' can not be null or empty.")
FileUtils.validateDirectoryIsNotEmpty(home)
switch(tool) {
case 'java': validateJava(home)
return
case 'mta': validateMta(home)
return
case 'neo': validateNeo(home)
return
case 'cm': validateCm(home)
return
default:
throw new AbortException("The tool \'$tool\' is not supported. The following tools are supported: java, mta, neo and cm.")
}
}
}
def validateJava(home) {
validateTool('Java', home, "$home/bin/java -version 2>&1", new Version(1,8,0))
}
def validateMta(home) {
validateTool('SAP Multitarget Application Archive Builder', home, "$JAVA_HOME/bin/java -jar $home/mta.jar -v", new Version(1, 0, 6))
}
def validateNeo(home) {
validateTool('SAP Cloud Platform Console Client', home, "$home/tools/neo.sh version", new Version(3, 39, 10))
}
def validateCm(home) {
validateTool('Change Management Command Line Interface', home, "$home/bin/cmclient -v", new Version(0, 0, 1))
}
def validateTool(name, home, command, expectedVersion) {
if (!name) throw new IllegalArgumentException("The parameter 'name' can not be null or empty.")
if (!home ) throw new IllegalArgumentException("The parameter 'home' can not be null or empty.")
if (!command ) throw new IllegalArgumentException("The parameter 'command' can not be null or empty.")
if (!expectedVersion) throw new IllegalArgumentException("The parameter 'expectedVersion' can not be null or empty.")
echo "[INFO] Validating $name version ${expectedVersion.toString()} or compatible version."
def output
try {
output = sh returnStdout: true, script: command
} catch(AbortException e) {
throw new AbortException("The validation of $name failed. Please check $name home '$home': $e.message.")
}
def version = new Version(output)
if (!version.isCompatibleVersion(expectedVersion)) {
throw new AbortException("The installed version of $name is ${version.toString()}. Please install version ${expectedVersion.toString()} or a compatible version.")
}
echo "[INFO] $name version ${version.toString()} is installed."
}