extend Analytics (#439)

* add extension mechanism for analytics

* add sha1 hashing

* correct return types

* correct registerEventListener method

* decrese visibility of createInstance

* correct typo

* catch exceptions from tests

* correct test case

* Update Analytics.groovy

* rename to Telemetry

* rename file

* fix typo

* add test case for generateSha1

* expose methods to tests

* add clear method for tests

* change return type

* add test for Telemetry class

* replace UtilsTests

* remove unused imports

* make default reporter static

* add stage parameters to payload

* simplify SHA1 method

* remove obsolete method

* remove obsolete methods

* remove outdated tests
This commit is contained in:
Christopher Fenner
2019-01-30 12:39:33 +01:00
committed by GitHub
parent a72e33f488
commit def66f4ffa
5 changed files with 269 additions and 91 deletions
+16 -57
View File
@@ -1,7 +1,10 @@
package com.sap.piper
import com.cloudbees.groovy.cps.NonCPS
import org.jenkinsci.plugins.workflow.steps.MissingContextVariableException
import com.sap.piper.analytics.Telemetry
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
@NonCPS
def getMandatoryParameter(Map map, paramName, defaultValue = null) {
@@ -72,67 +75,23 @@ def unstashAll(stashContent) {
return unstashedContent
}
def generateSha1Inline(input) {
return "`echo -n '${input}' | sha1sum | sed 's/ -//'`"
@NonCPS
def generateSha1(input) {
return MessageDigest
.getInstance("SHA-1")
.digest(input.getBytes(StandardCharsets.UTF_8))
.encodeHex().toString()
}
void pushToSWA(Map parameters, Map config) {
try {
//allow opt-out via configuration
if (!config?.collectTelemetryData) {
echo "[${parameters.get('step')}] Telemetry Report to SWA disabled!"
return
}
parameters.actionName = parameters.get('actionName') ?: 'Piper Library OS'
parameters.eventType = parameters.get('eventType') ?: 'library-os'
parameters.jobUrlSha1 = generateSha1(env.JOB_URL)
parameters.buildUrlSha1 = generateSha1(env.BUILD_URL)
def swaCustom = [:]
/* SWA custom parameters:
custom3 = step name (passed as parameter step)
custom4 = job url hashed (calculated)
custom5 = build url hashed (calculated)
custom10 = stage name
custom11 = step related parameter 1 (passed as parameter stepParam1)
custom12 = step related parameter 2 (passed as parameter stepParam2)
custom13 = step related parameter 3 (passed as parameter stepParam3)
custom14 = step related parameter 4 (passed as parameter stepParam4)
custom15 = step related parameter 5 (passed as parameter stepParam5)
*/
def swaUrl = 'https://webanalytics.cfapps.eu10.hana.ondemand.com/tracker/log'
def action_name = 'Piper Library OS'
def idsite = '827e8025-1e21-ae84-c3a3-3f62b70b0130'
def url = 'https://github.com/SAP/jenkins-library'
def event_type = parameters.get('eventType') ?: 'library-os'
swaCustom.custom3 = parameters.get('step')
swaCustom.custom4 = generateSha1Inline(env.JOB_URL)
swaCustom.custom5 = generateSha1Inline(env.BUILD_URL)
swaCustom.custom10 = parameters.get('stageName')
swaCustom.custom11 = parameters.get('stepParam1')
swaCustom.custom12 = parameters.get('stepParam2')
swaCustom.custom13 = parameters.get('stepParam3')
swaCustom.custom14 = parameters.get('stepParam4')
swaCustom.custom15 = parameters.get('stepParam5')
def options = []
options.push("-G")
options.push("-v \"${swaUrl}\"")
options.push("--data-urlencode \"action_name=${action_name}\"")
options.push("--data-urlencode \"idsite=${idsite}\"")
options.push("--data-urlencode \"url=${url}\"")
options.push("--data-urlencode \"event_type=${event_type}\"")
for(def key : ['custom3', 'custom4', 'custom5', 'custom10', 'custom11', 'custom12', 'custom13', 'custom14', 'custom15']){
if (swaCustom[key] != null) options.push("--data-urlencode \"${key}=${swaCustom[key]}\"")
}
options.push("--connect-timeout 5")
options.push("--max-time 20")
sh(returnStatus: true, script: "#!/bin/sh +x\ncurl ${options.join(' ')} > /dev/null 2>&1 || echo '[${parameters.get('step')}] Telemetry Report to SWA failed!'")
} catch (MissingContextVariableException noNode) {
echo "[${parameters.get('step')}] Telemetry Report to SWA skipped, no node available!"
Telemetry.notify(this, config, parameters)
} catch (ignore) {
// some error occured in SWA reporting. This should not break anything though.
// some error occured in telemetry reporting. This should not break anything though.
}
}
@@ -0,0 +1,97 @@
package com.sap.piper.analytics
import com.cloudbees.groovy.cps.NonCPS
import org.jenkinsci.plugins.workflow.steps.MissingContextVariableException
class Telemetry implements Serializable{
protected static Telemetry instance
protected List listenerList = []
protected Telemetry(){}
@NonCPS
protected static Telemetry getInstance(){
if(!instance) {
instance = new Telemetry()
registerListener({ steps, payload ->
piperOsDefaultReporting(steps, payload)
})
}
return instance
}
static void registerListener(Closure listener){
getInstance().listenerList.add(listener)
}
static notify(Script steps, Map config, Map payload){
//allow opt-out via configuration
if (!config?.collectTelemetryData) {
steps.echo "[${payload.step}] Telemetry reporting disabled!"
return
}
getInstance().listenerList.each { listener ->
try {
listener(steps, payload)
} catch (ignore) {
// some error occured in telemetry reporting. This should not break anything though.
steps.echo "[${payload.step}] Telemetry Report with listener failed: ${ignore.getMessage()}"
}
}
}
protected static void piperOsDefaultReporting(Script steps, Map payload) {
try {
def swaCustom = [:]
/* SWA custom parameters:
custom3 = step name (passed as parameter step)
custom4 = job url hashed (calculated)
custom5 = build url hashed (calculated)
custom10 = stage name
custom11 = step related parameter 1 (passed as parameter stepParam1)
custom12 = step related parameter 2 (passed as parameter stepParam2)
custom13 = step related parameter 3 (passed as parameter stepParam3)
custom14 = step related parameter 4 (passed as parameter stepParam4)
custom15 = step related parameter 5 (passed as parameter stepParam5)
*/
def swaUrl = 'https://webanalytics.cfapps.eu10.hana.ondemand.com/tracker/log'
def idsite = '827e8025-1e21-ae84-c3a3-3f62b70b0130'
def url = 'https://github.com/SAP/jenkins-library'
swaCustom.custom3 = payload.step
swaCustom.custom4 = payload.jobUrlSha1
swaCustom.custom5 = payload.buildUrlSha1
swaCustom.custom10 = payload.stageName
swaCustom.custom11 = payload.stepParam1
swaCustom.custom12 = payload.stepParam2
swaCustom.custom13 = payload.stepParam3
swaCustom.custom14 = payload.stepParam4
swaCustom.custom15 = payload.stepParam5
def options = []
options.push("-G")
options.push("-v \"${swaUrl}\"")
options.push("--data-urlencode \"action_name=${payload.actionName}\"")
options.push("--data-urlencode \"idsite=${idsite}\"")
options.push("--data-urlencode \"url=${url}\"")
options.push("--data-urlencode \"event_type=${payload.eventType}\"")
for(def key : ['custom3', 'custom4', 'custom5', 'custom10', 'custom11', 'custom12', 'custom13', 'custom14', 'custom15']){
if (swaCustom[key] != null) options.push("--data-urlencode \"${key}=${swaCustom[key]}\"")
}
options.push("--connect-timeout 5")
options.push("--max-time 20")
steps.sh(returnStatus: true, script: "#!/bin/sh +x\ncurl ${options.join(' ')} > /dev/null 2>&1 || echo '[${payload.step}] Telemetry Report to SWA failed!'")
} catch (MissingContextVariableException noNode) {
steps.echo "[${payload.step}] Telemetry Report to SWA skipped, no node available!"
}
}
}
+5 -32
View File
@@ -2,6 +2,7 @@ package com.sap.piper
import org.junit.Rule
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import static org.junit.Assert.assertThat
import org.junit.rules.ExpectedException
@@ -63,38 +64,10 @@ class UtilsTest extends BasePiperTest {
}
@Test
void testSWAReporting() {
utils.env = [BUILD_URL: 'something', JOB_URL: 'nothing']
utils.pushToSWA([step: 'anything'], [collectTelemetryData: true])
void testGenerateSHA1() {
def result = utils.generateSha1('ContinuousDelivery')
// asserts
assertThat(shellRule.shell, hasItem(containsString('curl -G -v "https://webanalytics.cfapps.eu10.hana.ondemand.com/tracker/log"')))
assertThat(shellRule.shell, hasItem(containsString('action_name=Piper Library OS')))
assertThat(shellRule.shell, hasItem(containsString('custom3=anything')))
assertThat(shellRule.shell, hasItem(containsString('custom5=`echo -n \'something\' | sha1sum | sed \'s/ -//\'`')))
}
@Test
void testDisabledSWAReporting() {
utils.env = [BUILD_URL: 'something', JOB_URL: 'nothing']
utils.pushToSWA([step: 'anything'], [collectTelemetryData: false])
// asserts
assertThat(loggingRule.log, containsString('[anything] Telemetry Report to SWA disabled!'))
assertThat(shellRule.shell, not(hasItem(containsString('https://webanalytics.cfapps.eu10.hana.ondemand.com'))))
}
@Test
void testImplicitlyDisabledSWAReporting() {
utils.env = [BUILD_URL: 'something', JOB_URL: 'nothing']
utils.pushToSWA([step: 'anything'], null)
// asserts
assertThat(loggingRule.log, containsString('[anything] Telemetry Report to SWA disabled!'))
}
@Test
void testImplicitlyDisabledSWAReporting2() {
utils.env = [BUILD_URL: 'something', JOB_URL: 'nothing']
utils.pushToSWA([step: 'anything'], [:])
// asserts
assertThat(loggingRule.log, containsString('[anything] Telemetry Report to SWA disabled!'))
// generated with "echo -n 'ContinuousDelivery' | sha1sum | sed 's/ -//'"
assertThat(result, is('0dad6c33b6246702132454f604dee80740f399ad'))
}
}
@@ -0,0 +1,145 @@
package com.sap.piper.analytics
import org.junit.Rule
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertThat
import static org.junit.Assume.assumeThat
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.hasItem
import static org.hamcrest.Matchers.is
import static org.hamcrest.Matchers.not
import static org.hamcrest.Matchers.empty
import util.JenkinsLoggingRule
import util.JenkinsShellCallRule
import util.BasePiperTest
import util.Rules
class TelemetryTest extends BasePiperTest {
private ExpectedException thrown = ExpectedException.none()
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private JenkinsShellCallRule jscr = new JenkinsShellCallRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(thrown)
.around(jscr)
.around(jlr)
private parameters
@Before
void setup() {
Telemetry.instance = null
parameters = [:]
}
@Test
void testCreateInstance() {
Telemetry.instance = new Telemetry()
// asserts
assertThat(Telemetry.getInstance().listenerList, is(empty()))
}
@Test
void testGetInstance() {
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
}
@Test
void testRegisterListenerAndNotify() {
// prepare
Map notificationPayload = [:]
Telemetry.instance = new Telemetry()
assumeThat(Telemetry.getInstance().listenerList, is(empty()))
Telemetry.registerListener({ steps, payload ->
notificationPayload = payload
})
// test
Telemetry.notify(nullScript, [collectTelemetryData: true], [step: 'anyStep', anything: 'something'])
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
assertThat(notificationPayload, is([step: 'anyStep', anything: 'something']))
}
@Test
void testNotifyWithOptOut() {
// prepare
Map notificationPayload = [:]
Telemetry.instance = new Telemetry()
assumeThat(Telemetry.getInstance().listenerList, is(empty()))
Telemetry.registerListener({ steps, payload ->
notificationPayload = payload
})
// test
Telemetry.notify(nullScript, [collectTelemetryData: false], [step: 'anyStep', anything: 'something'])
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
assertThat(jlr.log, containsString("[anyStep] Telemetry reporting disabled!"))
assertThat(notificationPayload.keySet(), is(empty()))
}
@Test
void testNotifyWithOptOutWithEmptyConfig() {
// prepare
Map notificationPayload = [:]
Telemetry.instance = new Telemetry()
assumeThat(Telemetry.getInstance().listenerList, is(empty()))
Telemetry.registerListener({ steps, payload ->
notificationPayload = payload
})
// test
Telemetry.notify(nullScript, [:], [step: 'anyStep', anything: 'something'])
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
assertThat(jlr.log, containsString("[anyStep] Telemetry reporting disabled!"))
assertThat(notificationPayload.keySet(), is(empty()))
}
@Test
void testNotifyWithOptOutWithoutConfig() {
// prepare
Map notificationPayload = [:]
Telemetry.instance = new Telemetry()
assumeThat(Telemetry.getInstance().listenerList, is(empty()))
Telemetry.registerListener({ steps, payload ->
notificationPayload = payload
})
// test
Telemetry.notify(nullScript, null, [step: 'anyStep', anything: 'something'])
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
assertThat(jlr.log, containsString("[anyStep] Telemetry reporting disabled!"))
assertThat(notificationPayload.keySet(), is(empty()))
}
@Test
void testReportingToSWA() {
// prepare
assumeThat(Telemetry.getInstance().listenerList, is(not(empty())))
// test
Telemetry.notify(nullScript, [collectTelemetryData: true], [
actionName: 'Piper Library OS',
eventType: 'library-os',
jobUrlSha1: '1234',
buildUrlSha1: 'abcd',
step: 'anyStep',
stepParam1: 'something'
])
// asserts
assertThat(jscr.shell, hasItem(containsString('curl -G -v "https://webanalytics.cfapps.eu10.hana.ondemand.com/tracker/log"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "action_name=Piper Library OS"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "event_type=library-os"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "custom3=anyStep"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "custom4=1234"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "custom5=abcd"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "custom11=something"')))
}
}
+6 -2
View File
@@ -54,7 +54,6 @@ private void stageLocking(Map config, Closure body) {
}
private void executeStage(script, originalStage, stageName, config, utils) {
boolean projectExtensions
boolean globalExtensions
def startTime = System.currentTimeMillis()
@@ -108,14 +107,19 @@ private void executeStage(script, originalStage, stageName, config, utils) {
stageName: stageName,
stepParamKey1: 'buildResult',
stepParam1: "${script.currentBuild.currentResult}",
buildResult: "${script.currentBuild.currentResult}",
stepParamKey2: 'stageStartTime',
stepParam2: "${startTime}",
stageStartTime: "${startTime}",
stepParamKey3: 'stageDuration',
stepParam3: "${duration}",
stageDuration: "${duration}",
stepParamKey4: 'projectExtension',
stepParam4: "${projectExtensions}",
projectExtension: "${projectExtensions}",
stepParamKey5: 'globalExtension',
stepParam5: "${globalExtensions}"
stepParam5: "${globalExtensions}",
globalExtension: "${globalExtensions}"
], config)
}
}