1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-18 05:18:24 +02:00

Merge branch 'master' into pr/stepReturnVoid

This commit is contained in:
Christopher Fenner 2018-10-17 11:50:58 +02:00 committed by GitHub
commit dba20d496c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 321 additions and 5 deletions

View File

@ -61,6 +61,10 @@ Deployment can be done
- cfOrg - cfOrg
- cfSpace - cfSpace
!!! note
Due to [an incompatible change](https://github.com/cloudfoundry/cli/issues/1445) in the Cloud Foundry CLI, multiple buildpacks are not supported by this step.
If your `application` contains a list of `buildpacks` instead a single `buildpack`, this will be automatically re-written by the step when blue-green deployment is used.
* `deployTool` defines the tool which should be used for deployment. * `deployTool` defines the tool which should be used for deployment.
* `deployType` defines the type of deployment, either `standard` deployment which results in a system downtime or a zero-downtime `blue-green` deployment. * `deployType` defines the type of deployment, either `standard` deployment which results in a system downtime or a zero-downtime `blue-green` deployment.
* `dockerImage` defines the Docker image containing the deployment tools (like cf cli, ...) and `dockerWorkspace` defines the home directory of the default user of the `dockerImage` * `dockerImage` defines the Docker image containing the deployment tools (like cf cli, ...) and `dockerWorkspace` defines the home directory of the default user of the `dockerImage`
@ -106,7 +110,11 @@ The following parameters can also be specified as step/stage/general parameters
## Example ## Example
```groovy ```groovy
artifactSetVersion script: this, buildTool: 'maven' cloudFoundryDeploy(
script: script,
deployType: 'blue-green',
cloudFoundry: [apiEndpoint: 'https://test.server.com', appName:'cfAppName', credentialsId: 'cfCredentialsId', manifest: 'cfManifest', org: 'cfOrg', space: 'cfSpace'],
deployTool: 'cf_native'
)
``` ```

View File

@ -0,0 +1,62 @@
# healthExecuteCheck
## Description
Calls the health endpoint url of the application.
The intention of the check is to verify that a suitable health endpoint is available. Such a health endpoint is required for operation purposes.
This check is used as a real-life test for your productive health endpoints.
!!! note "Check Depth"
Typically, tools performing simple health checks are not too smart. Therefore it is important to choose an endpoint for checking wisely.
This check therefore only checks if the application/service url returns `HTTP 200`.
This is in line with health check capabilities of platforms which are used for example in load balancing scenarios. Here you can find an [example for Amazon AWS](http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-healthchecks.html).
## Prerequisites
Endpoint for health check is configured.
!!! warning
The health endpoint needs to be available without authentication!
!!! tip
If using Spring Boot framework, ideally the provided `/health` endpoint is used and extended by development. Further information can be found in the [Spring Boot documenation for Endpoints](http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html)
## Example
Pipeline step:
```groovy
healthExecuteCheck testServerUrl: 'https://testserver.com'
```
## Parameters
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
|script|yes|||
|healthEndpoint|no|``||
|testServerUrl|no|||
Details:
* `script` defines the global script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for storing the measured duration.
* Health check function is called providing full qualified `testServerUrl` (and optionally with `healthEndpoint` if endpoint is not the standard url) to the health check.
* In case response of the call is different than `HTTP 200 OK` the **health check fails and the pipeline stops**.
## Step configuration
We recommend to define values of step parameters via [config.yml file](../configuration.md).
In following sections the configuration is possible:
| parameter | general | step | stage |
| ----------|-----------|---------|-----------------|
|script||||
|healthEndpoint|X|X|X|
|testServerUrl|X|X|X|

View File

@ -12,6 +12,7 @@ nav:
- dockerExecuteOnKubernetes: steps/dockerExecuteOnKubernetes.md - dockerExecuteOnKubernetes: steps/dockerExecuteOnKubernetes.md
- durationMeasure: steps/durationMeasure.md - durationMeasure: steps/durationMeasure.md
- handlePipelineStepErrors: steps/handlePipelineStepErrors.md - handlePipelineStepErrors: steps/handlePipelineStepErrors.md
- healthExecuteCheck: steps/healthExecuteCheck.md
- influxWriteData: steps/influxWriteData.md - influxWriteData: steps/influxWriteData.md
- mavenExecute: steps/mavenExecute.md - mavenExecute: steps/mavenExecute.md
- mtaBuild: steps/mtaBuild.md - mtaBuild: steps/mtaBuild.md

View File

@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.sap.cp.jenkins</groupId> <groupId>com.sap.cp.jenkins</groupId>
<artifactId>jenkins-library</artifactId> <artifactId>jenkins-library</artifactId>
<version>0.7</version> <version>0.8</version>
<name>SAP CP Piper Library</name> <name>SAP CP Piper Library</name>
<description>Shared library containing steps and utilities to set up continuous deployment processes for SAP technologies.</description> <description>Shared library containing steps and utilities to set up continuous deployment processes for SAP technologies.</description>

View File

@ -139,6 +139,8 @@ steps:
workspace: '**/*.*' workspace: '**/*.*'
stashExcludes: stashExcludes:
workspace: 'nohup.out' workspace: 'nohup.out'
healthExecuteCheck:
healthEndpoint: ''
influxWriteData: influxWriteData:
influxServer: 'jenkins' influxServer: 'jenkins'
mavenExecute: mavenExecute:

View File

@ -0,0 +1,26 @@
package com.sap.piper
import com.cloudbees.groovy.cps.NonCPS
class CfManifestUtils {
@NonCPS
static Map transform(Map manifest) {
if (manifest.applications[0].buildpacks) {
manifest['applications'].each { Map application ->
def buildpacks = application['buildpacks']
if (buildpacks) {
if (buildpacks instanceof List) {
if (buildpacks.size > 1) {
throw new RuntimeException('More than one Cloud Foundry Buildpack is not supported. Please check your manifest.yaml file.')
}
application['buildpack'] = buildpacks[0]
application.remove('buildpacks')
} else {
throw new RuntimeException('"buildpacks" in manifest.yaml is not a list. Please check your manifest.yaml file.')
}
}
}
}
return manifest
}
}

View File

@ -105,6 +105,11 @@ class CloudFoundryDeployTest extends BasePiperTest {
@Test @Test
void testCfNativeWithAppName() { void testCfNativeWithAppName() {
jryr.registerYaml('test.yml', "applications: [[name: 'manifestAppName']]")
helper.registerAllowedMethod('writeYaml', [Map], { Map parameters ->
generatedFile = parameters.file
data = parameters.data
})
jsr.step.cloudFoundryDeploy([ jsr.step.cloudFoundryDeploy([
script: nullScript, script: nullScript,
juStabUtils: utils, juStabUtils: utils,
@ -125,6 +130,11 @@ class CloudFoundryDeployTest extends BasePiperTest {
@Test @Test
void testCfNativeWithAppNameCustomApi() { void testCfNativeWithAppNameCustomApi() {
jryr.registerYaml('test.yml', "applications: [[name: 'manifestAppName']]")
helper.registerAllowedMethod('writeYaml', [Map], { Map parameters ->
generatedFile = parameters.file
data = parameters.data
})
jsr.step.cloudFoundryDeploy([ jsr.step.cloudFoundryDeploy([
script: nullScript, script: nullScript,
juStabUtils: utils, juStabUtils: utils,
@ -142,6 +152,11 @@ class CloudFoundryDeployTest extends BasePiperTest {
@Test @Test
void testCfNativeWithAppNameCompatible() { void testCfNativeWithAppNameCompatible() {
jryr.registerYaml('test.yml', "applications: [[name: 'manifestAppName']]")
helper.registerAllowedMethod('writeYaml', [Map], { Map parameters ->
generatedFile = parameters.file
data = parameters.data
})
jsr.step.cloudFoundryDeploy([ jsr.step.cloudFoundryDeploy([
script: nullScript, script: nullScript,
juStabUtils: utils, juStabUtils: utils,
@ -165,7 +180,11 @@ class CloudFoundryDeployTest extends BasePiperTest {
@Test @Test
void testCfNativeAppNameFromManifest() { void testCfNativeAppNameFromManifest() {
helper.registerAllowedMethod('fileExists', [String.class], { s -> return true }) helper.registerAllowedMethod('fileExists', [String.class], { s -> return true })
jryr.registerYaml('test.yml', "[applications: [[name: 'manifestAppName']]]") jryr.registerYaml('test.yml', "applications: [[name: 'manifestAppName']]")
helper.registerAllowedMethod('writeYaml', [Map], { Map parameters ->
generatedFile = parameters.file
data = parameters.data
})
jsr.step.cloudFoundryDeploy([ jsr.step.cloudFoundryDeploy([
script: nullScript, script: nullScript,
@ -185,6 +204,10 @@ class CloudFoundryDeployTest extends BasePiperTest {
void testCfNativeWithoutAppName() { void testCfNativeWithoutAppName() {
helper.registerAllowedMethod('fileExists', [String.class], { s -> return true }) helper.registerAllowedMethod('fileExists', [String.class], { s -> return true })
jryr.registerYaml('test.yml', "applications: [[]]") jryr.registerYaml('test.yml', "applications: [[]]")
helper.registerAllowedMethod('writeYaml', [Map], { Map parameters ->
generatedFile = parameters.file
data = parameters.data
})
thrown.expect(hudson.AbortException) thrown.expect(hudson.AbortException)
thrown.expectMessage('[cloudFoundryDeploy] ERROR: No appName available in manifest test.yml.') thrown.expectMessage('[cloudFoundryDeploy] ERROR: No appName available in manifest test.yml.')

View File

@ -0,0 +1,88 @@
#!groovy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
import util.JenkinsStepRule
import util.Rules
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
class HealthExecuteCheckTest extends BasePiperTest {
private JenkinsStepRule jsr = new JenkinsStepRule(this)
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private ExpectedException thrown = ExpectedException.none()
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(jlr)
.around(jsr)
.around(thrown)
@Before
void init() throws Exception {
// register Jenkins commands with mock values
def command1 = "curl -so /dev/null -w '%{response_code}' http://testserver"
def command2 = "curl -so /dev/null -w '%{response_code}' http://testserver/endpoint"
helper.registerAllowedMethod('sh', [Map.class], {map ->
return map.script == command1 || map.script == command2 ? "200" : "404"
})
}
@Test
void testHealthCheckOk() throws Exception {
def testUrl = 'http://testserver/endpoint'
jsr.step.healthExecuteCheck(
script: nullScript,
testServerUrl: testUrl
)
assertThat(jlr.log, containsString("Health check for ${testUrl} successful"))
}
@Test
void testHealthCheck404() throws Exception {
def testUrl = 'http://testserver/404'
thrown.expect(Exception)
thrown.expectMessage('Health check failed: 404')
jsr.step.healthExecuteCheck(
script: nullScript,
testServerUrl: testUrl
)
}
@Test
void testHealthCheckWithEndPoint() throws Exception {
jsr.step.healthExecuteCheck(
script: nullScript,
testServerUrl: 'http://testserver',
healthEndpoint: 'endpoint'
)
assertThat(jlr.log, containsString("Health check for http://testserver/endpoint successful"))
}
@Test
void testHealthCheckWithEndPointTrailingSlash() throws Exception {
jsr.step.healthExecuteCheck(
script: nullScript,
testServerUrl: 'http://testserver/',
healthEndpoint: 'endpoint'
)
assertThat(jlr.log, containsString("Health check for http://testserver/endpoint successful"))
}
}

View File

@ -0,0 +1,30 @@
package com.sap.piper
import org.junit.Test
import static org.junit.Assert.*
class CfManifestUtilsTest {
@Test
void testManifestTransform() {
Map testFixture = [applications: [[buildpacks: ['sap_java_buildpack']]]]
Map expected = [applications: [[buildpack: 'sap_java_buildpack']]]
def actual = CfManifestUtils.transform(testFixture)
assertEquals(expected, actual)
}
@Test(expected = RuntimeException)
void testManifestTransformMultipleBuildpacks() {
Map testFixture = [applications: [[buildpacks: ['sap_java_buildpack', 'another_buildpack']]]]
CfManifestUtils.transform(testFixture)
}
@Test
void testManifestTransformShouldNotChange() {
Map testFixture = [applications: [[buildpack: 'sap_java_buildpack']]]
Map expected = [applications: [[buildpack: 'sap_java_buildpack']]]
def actual = CfManifestUtils.transform(testFixture)
assertEquals(expected, actual)
}
}

View File

@ -1,5 +1,6 @@
import com.sap.piper.Utils import com.sap.piper.Utils
import com.sap.piper.ConfigurationHelper import com.sap.piper.ConfigurationHelper
import com.sap.piper.CfManifestUtils
import groovy.transform.Field import groovy.transform.Field
@ -112,6 +113,7 @@ def deployCfNative (config) {
def deployCommand = 'push' def deployCommand = 'push'
if (config.deployType == 'blue-green') { if (config.deployType == 'blue-green') {
deployCommand = 'blue-green-deploy' deployCommand = 'blue-green-deploy'
handleLegacyCfManifest(config)
} else { } else {
config.smokeTest = '' config.smokeTest = ''
} }
@ -129,7 +131,7 @@ def deployCfNative (config) {
} }
sh """#!/bin/bash sh """#!/bin/bash
set +x set +x
export HOME=${config.dockerWorkspace} export HOME=${config.dockerWorkspace}
cf login -u \"${username}\" -p '${password}' -a ${config.cloudFoundry.apiEndpoint} -o \"${config.cloudFoundry.org}\" -s \"${config.cloudFoundry.space}\" cf login -u \"${username}\" -p '${password}' -a ${config.cloudFoundry.apiEndpoint} -o \"${config.cloudFoundry.org}\" -s \"${config.cloudFoundry.space}\"
cf plugins cf plugins
@ -166,3 +168,18 @@ def deployMta (config) {
sh "cf logout" sh "cf logout"
} }
} }
def handleLegacyCfManifest(config) {
def manifest = readYaml file: config.cloudFoundry.manifest
String originalManifest = manifest.toString()
manifest = CfManifestUtils.transform(manifest)
String transformedManifest = manifest.toString()
if (originalManifest != transformedManifest) {
echo """The file ${config.cloudFoundry.manifest} is not compatible with the Cloud Foundry blue-green deployment plugin. Re-writing inline.
See this issue if you are interested in the background: https://github.com/cloudfoundry/cli/issues/1445.\n
Original manifest file content: $originalManifest\n
Transformed manifest file content: $transformedManifest"""
sh "rm ${config.cloudFoundry.manifest}"
writeYaml file: config.cloudFoundry.manifest, data: manifest
}
}

View File

@ -7,6 +7,12 @@ class commonPipelineEnvironment implements Serializable {
//stores the gitCommitId as well as additional git information for the build during pipeline run //stores the gitCommitId as well as additional git information for the build during pipeline run
String gitCommitId String gitCommitId
String gitSshUrl String gitSshUrl
String gitHttpsUrl
String gitBranch
//GiutHub specific information
String githubOrg
String githubRepo
//stores properties for a pipeline which build an artifact and then bundles it into a container //stores properties for a pipeline which build an artifact and then bundles it into a container
private Map appContainerProperties = [:] private Map appContainerProperties = [:]
@ -30,6 +36,11 @@ class commonPipelineEnvironment implements Serializable {
gitCommitId = null gitCommitId = null
gitSshUrl = null gitSshUrl = null
gitHttpsUrl = null
gitBranch = null
githubOrg = null
githubRepo = null
influxCustomData = [:] influxCustomData = [:]
influxCustomDataMap = [pipeline_data: [:], step_data: [:]] influxCustomDataMap = [pipeline_data: [:], step_data: [:]]

View File

@ -0,0 +1,48 @@
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import groovy.transform.Field
@Field String STEP_NAME = 'healthExecuteCheck'
@Field Set STEP_CONFIG_KEYS = [
'healthEndpoint',
'testServerUrl'
]
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
void call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
def script = parameters?.script ?: [commonPipelineEnvironment: commonPipelineEnvironment]
// load default & individual configuration
Map config = ConfigurationHelper
.loadStepDefaults(this)
.mixinGeneralConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.withMandatoryProperty('testServerUrl')
.use()
new Utils().pushToSWA([step: STEP_NAME], config)
def checkUrl = config.testServerUrl
if(config.healthEndpoint){
if(!checkUrl.endsWith('/'))
checkUrl += '/'
checkUrl += config.healthEndpoint
}
def statusCode = curl(checkUrl)
if (statusCode != '200') {
error "Health check failed: ${statusCode}"
} else {
echo "Health check for ${checkUrl} successful"
}
}
}
def curl(url){
return sh(
returnStdout: true,
script: "curl -so /dev/null -w '%{response_code}' ${url}"
).trim()
}