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

feat(go): add telemetry reporting (#1100)

* Add telemetry support

* First round telemetry

* Add telemetry flag

* fix: move files to avoid import cycles

* add noTelemetry as global config option

* Respect telemetry configuration for reporting

* add site id, swa endpoint

* correct logger initialization

* add http logic

* rename init method

* rename consts & types

* convert struct to payload

* convert data to payload string

* move activation flag out of data structure

* extract types to own file

* build query using net/url

* correct field mapping

* extract notify coding to own file

* cleanup parameter mapping

* preare base data

* fix codeclimate issue

* correct test case

* fill values from env

* test all fields

* untrack notify.go

* ignore empty custom values

* cleanup data.go

* add test cases

* cleanup

* add usage reporting to karma step

* add usage reporting to step generator

* externalise siteID

* correct custom field names

* test env handling

* simplify method signature

* revert parameter negation

* correct import

* adjust golden file

* inclease log level

* ignore test case

* Revert "inclease log level"

This reverts commit 70cae0e0296afb2aa9e7d71e83ea70aa83d1a6d7.

* add test case for envvars

* remove duplicate reporting

* remove duplicate reporting

* correct format

* regenerate checkmarx file

* add log message on deactivation

* rename function

* add comments to understand SWA mapping

Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
Christopher Fenner 2020-01-29 13:17:54 +01:00 committed by GitHub
parent 92441577d8
commit aa3fb8adb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 429 additions and 20 deletions

View File

@ -12,7 +12,8 @@ RUN export GIT_COMMIT=$(git rev-parse HEAD) && \
CGO_ENABLED=0 go build \
-ldflags \
"-X github.com/SAP/jenkins-library/cmd.GitCommit=${GIT_COMMIT} \
-X github.com/SAP/jenkins-library/pkg/log.LibraryRepository=${GIT_REPOSITORY}" \
-X github.com/SAP/jenkins-library/pkg/log.LibraryRepository=${GIT_REPOSITORY} \
-X github.com/SAP/jenkins-library/pkg/telemetry.LibraryRepository=${GIT_REPOSITORY}" \
-o piper
# FROM gcr.io/distroless/base:latest

View File

@ -8,6 +8,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
@ -33,7 +34,6 @@ type checkmarxExecuteScanOptions struct {
VulnerabilityThresholdMedium int `json:"vulnerabilityThresholdMedium,omitempty"`
VulnerabilityThresholdResult string `json:"vulnerabilityThresholdResult,omitempty"`
VulnerabilityThresholdUnit string `json:"vulnerabilityThresholdUnit,omitempty"`
Verbose bool `json:"verbose,omitempty"`
}
type checkmarxExecuteScanInflux struct {
@ -186,6 +186,8 @@ thresholds instead of ` + "`" + `percentage` + "`" + ` whereas we strongly recom
}
log.DeferExitHandler(handler)
defer handler()
telemetry.Initialize(GeneralConfig.NoTelemetry, "checkmarxExecuteScan")
telemetry.Send(&telemetry.CustomData{})
return checkmarxExecuteScan(myCheckmarxExecuteScanOptions, &influx)
},
}
@ -216,7 +218,6 @@ func addCheckmarxExecuteScanFlags(cmd *cobra.Command) {
cmd.Flags().IntVar(&myCheckmarxExecuteScanOptions.VulnerabilityThresholdMedium, "vulnerabilityThresholdMedium", 100, "The specific threshold for medium severity findings")
cmd.Flags().StringVar(&myCheckmarxExecuteScanOptions.VulnerabilityThresholdResult, "vulnerabilityThresholdResult", "FAILURE", "The result of the build in case thresholds are enabled and exceeded")
cmd.Flags().StringVar(&myCheckmarxExecuteScanOptions.VulnerabilityThresholdUnit, "vulnerabilityThresholdUnit", "percentage", "The unit for the threshold to apply.")
cmd.Flags().BoolVar(&myCheckmarxExecuteScanOptions.Verbose, "verbose", false, "Whether the step shall provide verbose logging output")
cmd.MarkFlagRequired("password")
cmd.MarkFlagRequired("projectName")
@ -398,14 +399,6 @@ func checkmarxExecuteScanMetadata() config.StepData {
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "verbose",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
},
},
},
},

View File

@ -5,6 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
@ -37,6 +38,8 @@ func DetectExecuteScanCommand() *cobra.Command {
},
RunE: func(cmd *cobra.Command, args []string) error {
telemetry.Initialize(GeneralConfig.NoTelemetry, "detectExecuteScan")
telemetry.Send(&telemetry.CustomData{})
return detectExecuteScan(myDetectExecuteScanOptions)
},
}

View File

@ -5,6 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
@ -42,6 +43,8 @@ It can for example be used for GitOps scenarios or for scenarios where you want
},
RunE: func(cmd *cobra.Command, args []string) error {
telemetry.Initialize(GeneralConfig.NoTelemetry, "githubCreatePullRequest")
telemetry.Send(&telemetry.CustomData{})
return githubCreatePullRequest(myGithubCreatePullRequestOptions)
},
}

View File

@ -5,6 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
@ -52,6 +53,8 @@ The result looks like
},
RunE: func(cmd *cobra.Command, args []string) error {
telemetry.Initialize(GeneralConfig.NoTelemetry, "githubPublishRelease")
telemetry.Send(&telemetry.CustomData{})
return githubPublishRelease(myGithubPublishReleaseOptions)
},
}

View File

@ -3,6 +3,7 @@ package cmd
import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
@ -40,6 +41,8 @@ In the Docker network, the containers can be referenced by the values provided i
},
RunE: func(cmd *cobra.Command, args []string) error {
telemetry.Initialize(GeneralConfig.NoTelemetry, "karmaExecuteTests")
telemetry.Send(&telemetry.CustomData{})
return karmaExecuteTests(myKarmaExecuteTestsOptions)
},
}

View File

@ -5,6 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
@ -65,6 +66,8 @@ helm upgrade <deploymentName> <chartPath> --install --force --namespace <namespa
},
RunE: func(cmd *cobra.Command, args []string) error {
telemetry.Initialize(GeneralConfig.NoTelemetry, "kubernetesDeploy")
telemetry.Send(&telemetry.CustomData{})
return kubernetesDeploy(myKubernetesDeployOptions)
},
}

View File

@ -19,6 +19,7 @@ type GeneralConfigOptions struct {
DefaultConfig []string //ordered list of Piper default configurations. Can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
ParametersJSON string
EnvRootPath string
NoTelemetry bool
StageName string
StepConfigJSON string
StepMetadata string //metadata to be considered, can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
@ -67,6 +68,7 @@ func addRootFlags(rootCmd *cobra.Command) {
rootCmd.PersistentFlags().StringVar(&GeneralConfig.EnvRootPath, "envRootPath", ".pipeline", "Root path to Piper pipeline shared environments")
rootCmd.PersistentFlags().StringVar(&GeneralConfig.StageName, "stageName", os.Getenv("STAGE_NAME"), "Name of the stage for which configuration should be included")
rootCmd.PersistentFlags().StringVar(&GeneralConfig.StepConfigJSON, "stepConfigJSON", os.Getenv("PIPER_stepConfigJSON"), "Step configuration in JSON format")
rootCmd.PersistentFlags().BoolVar(&GeneralConfig.NoTelemetry, "noTelemetry", false, "Disables telemetry reporting")
rootCmd.PersistentFlags().BoolVarP(&GeneralConfig.Verbose, "verbose", "v", false, "verbose output")
}
@ -75,6 +77,12 @@ func addRootFlags(rootCmd *cobra.Command) {
func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName string, options interface{}, openFile func(s string) (io.ReadCloser, error)) error {
filters := metadata.GetParameterFilters()
// add telemetry parameter "collectTelemetryData" to ALL, GENERAL and PARAMETER filters
filters.All = append(filters.All, "collectTelemetryData")
filters.General = append(filters.General, "collectTelemetryData")
filters.Parameters = append(filters.Parameters, "collectTelemetryData")
resourceParams := metadata.GetResourceParameters(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
flagValues := config.AvailableFlagValues(cmd, &filters)
@ -119,6 +127,10 @@ func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName strin
}
}
if fmt.Sprintf("%v", stepConfig.Config["collectTelemetryData"]) == "false" {
GeneralConfig.NoTelemetry = true
}
if !GeneralConfig.Verbose {
if stepConfig.Config["verbose"] != nil && stepConfig.Config["verbose"].(bool) {
log.SetVerbose(stepConfig.Config["verbose"].(bool))

View File

@ -3,6 +3,7 @@ package cmd
import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
@ -27,6 +28,8 @@ func VersionCommand() *cobra.Command {
},
RunE: func(cmd *cobra.Command, args []string) error {
telemetry.Initialize(GeneralConfig.NoTelemetry, "version")
telemetry.Send(&telemetry.CustomData{})
return version(myVersionOptions)
},
}

View File

@ -5,6 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
@ -42,6 +43,8 @@ func XsDeployCommand() *cobra.Command {
},
RunE: func(cmd *cobra.Command, args []string) error {
telemetry.Initialize(GeneralConfig.NoTelemetry, "xsDeploy")
telemetry.Send(&telemetry.CustomData{})
return xsDeploy(myXsDeployOptions)
},
}

8
go.mod
View File

@ -13,9 +13,11 @@ require (
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 // indirect
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.4 // indirect
)

18
go.sum
View File

@ -31,7 +31,9 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -62,15 +64,15 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -83,12 +85,16 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -39,6 +39,7 @@ import (
{{ if .ExportPrefix}}{{ .ExportPrefix }} "github.com/SAP/jenkins-library/cmd"{{ end -}}
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
{{ if .OutputResources }}"github.com/SAP/jenkins-library/pkg/piperenv"{{ end }}
"github.com/spf13/cobra"
)
@ -78,6 +79,8 @@ func {{.CobraCmdFuncName}}() *cobra.Command {
log.DeferExitHandler(handler)
defer handler()
{{- end }}
telemetry.Initialize(GeneralConfig.NoTelemetry, "{{ .StepName }}")
telemetry.Send(&telemetry.CustomData{})
return {{.StepName}}(my{{ .StepName | title }}Options{{ range $notused, $oRes := .OutputResources}}, &{{ index $oRes "name" }}{{ end }})
},
}

View File

@ -7,6 +7,7 @@ import (
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/spf13/cobra"
)
@ -110,6 +111,8 @@ func TestStepCommand() *cobra.Command {
}
log.DeferExitHandler(handler)
defer handler()
telemetry.Initialize(GeneralConfig.NoTelemetry, "testStep")
telemetry.Send(&telemetry.CustomData{})
return testStep(myTestStepOptions, &commonPipelineEnvironment, &influxTest)
},
}

View File

@ -130,6 +130,7 @@ func (c *Client) SetOptions(options ClientOptions) {
func (c *Client) initialize() *http.Client {
c.applyDefaults()
c.logger = log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")
var httpClient = &http.Client{
Timeout: c.timeout,

82
pkg/telemetry/data.go Normal file
View File

@ -0,0 +1,82 @@
package telemetry
import (
"encoding/json"
"net/url"
)
// BaseData object definition containing the base data and it's mapping information
type BaseData struct {
// SWA receives the fields custom1 - custom30 and e_a, e_2 - e_30 for custom values.
ActionName string `json:"action_name"`
EventType string `json:"event_type"`
SiteID string `json:"idsite"`
URL string `json:"url"`
StepName string `json:"e_3"` // set by step generator
StageName string `json:"e_10"`
PipelineURLHash string `json:"e_4"` // defaults to sha1 of env.JOB_URl
BuildURLHash string `json:"e_5"` // defaults to sha1 of env.BUILD_URL
}
var baseData BaseData
// BaseMetaData object definition containing the labels for the base data and it's mapping information
type BaseMetaData struct {
// SWA receives the fields custom1 - custom30 and e_a, e_2 - e_30 for custom values.
StepNameLabel string `json:"custom3"`
StageNameLabel string `json:"custom10"`
PipelineURLHashLabel string `json:"custom4"`
BuildURLHashLabel string `json:"custom5"`
}
// baseMetaData object containing the labels for the base data
var baseMetaData BaseMetaData = BaseMetaData{
StepNameLabel: "stepName",
StageNameLabel: "stageName",
PipelineURLHashLabel: "pipelineUrlHash",
BuildURLHashLabel: "buildUrlHash",
}
// CustomData object definition containing the data that can be set by a step and it's mapping information
type CustomData struct {
// SWA receives the fields custom1 - custom30 and e_a, e_2 - e_30 for custom values.
// Piper uses the values custom11 - custom25 & e_11 - e_25 for library related reporting
// and custom26 - custom30 & e_26 - e_30 for step related reporting.
Custom1Label string `json:"custom26,omitempty"`
Custom2Label string `json:"custom27,omitempty"`
Custom3Label string `json:"custom28,omitempty"`
Custom4Label string `json:"custom29,omitempty"`
Custom5Label string `json:"custom30,omitempty"`
Custom1 string `json:"e_26,omitempty"`
Custom2 string `json:"e_27,omitempty"`
Custom3 string `json:"e_28,omitempty"`
Custom4 string `json:"e_29,omitempty"`
Custom5 string `json:"e_30,omitempty"`
}
// Data object definition containing all telemetry data
type Data struct {
BaseData
BaseMetaData
CustomData
}
// toMap transfers the data object into a map using JSON tags
func (d *Data) toMap() (result map[string]string) {
jsonObj, _ := json.Marshal(d)
json.Unmarshal(jsonObj, &result)
return
}
// toPayloadString transfers the data object into a 'key=value&..' string
func (d *Data) toPayloadString() string {
parameters := url.Values{}
for key, value := range d.toMap() {
if len(value) > 0 {
parameters.Add(key, value)
}
}
return parameters.Encode()
}

View File

@ -0,0 +1,67 @@
package telemetry
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDataToMap(t *testing.T) {
// init
testData := Data{BaseData: BaseData{ActionName: "testAction"}, CustomData: CustomData{Custom2Label: "label", Custom2: "value"}}
// test
result := testData.toMap()
// assert
assert.Contains(t, result, "action_name")
assert.Contains(t, result, "event_type")
assert.Contains(t, result, "idsite")
assert.Contains(t, result, "url")
assert.Contains(t, result, "e_3")
assert.Contains(t, result, "e_4")
assert.Contains(t, result, "e_5")
assert.Contains(t, result, "e_10")
assert.Contains(t, result, "custom3")
assert.Contains(t, result, "custom4")
assert.Contains(t, result, "custom5")
assert.Contains(t, result, "custom10")
assert.Contains(t, result, "e_27")
assert.Contains(t, result, "custom27")
assert.Equal(t, 14, len(result))
}
func TestDataToPayload(t *testing.T) {
t.Run("with single parameter", func(t *testing.T) {
// init
testData := Data{BaseData: BaseData{ActionName: "testAction"}}
// test
result := testData.toPayloadString()
// assert
assert.Contains(t, result, "action_name=testAction")
assert.NotContains(t, result, "idsite=")
})
t.Run("with multiple parameters", func(t *testing.T) {
// init
testData := Data{BaseData: BaseData{ActionName: "testAction", SiteID: "gl8rkd6j211bw3j1fwb8rb4h0000gn"}}
// test
result := testData.toPayloadString()
// assert
assert.Contains(t, result, "&")
assert.Contains(t, result, "action_name=testAction")
assert.Contains(t, result, "idsite=gl8rkd6j211bw3j1fwb8rb4h0000gn")
})
t.Run("encoding", func(t *testing.T) {
// init
testData := Data{BaseData: BaseData{ActionName: "t€štÄçtïøñ"}}
// test
result := testData.toPayloadString()
// assert
assert.Contains(t, result, "t%E2%82%AC%C5%A1t%C3%84%C3%A7t%C3%AF%C3%B8%C3%B1")
assert.NotContains(t, result, "t€štÄçtïøñ")
})
}

107
pkg/telemetry/telemetry.go Normal file
View File

@ -0,0 +1,107 @@
package telemetry
import (
"crypto/sha1"
"fmt"
"os"
"time"
"net/http"
"net/url"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
)
// eventType
const eventType = "library-os-ng"
// actionName
const actionName = "Piper Library OS"
// LibraryRepository that is passed into with -ldflags
var LibraryRepository string
// SiteID ...
var SiteID string
var disabled bool
var client piperhttp.Sender
// Initialize sets up the base telemetry data and is called in generated part of the steps
func Initialize(telemetryDisabled bool, stepName string) {
disabled = telemetryDisabled
// skip if telemetry is dieabled
if disabled {
log.Entry().Info("Telemetry reporting deactivated")
return
}
if client == nil {
client = &piperhttp.Client{}
}
client.SetOptions(piperhttp.ClientOptions{Timeout: time.Second * 5})
if len(LibraryRepository) == 0 {
LibraryRepository = "https://github.com/n/a"
}
if len(SiteID) == 0 {
SiteID = "827e8025-1e21-ae84-c3a3-3f62b70b0130"
}
baseData = BaseData{
URL: LibraryRepository,
ActionName: actionName,
EventType: eventType,
StepName: stepName,
SiteID: SiteID,
PipelineURLHash: getPipelineURLHash(), // http://server:port/jenkins/job/foo/
BuildURLHash: getBuildURLHash(), // http://server:port/jenkins/job/foo/15/
}
//ToDo: register Logrus Hook
}
func getPipelineURLHash() string {
return toSha1OrNA(os.Getenv("JOB_URL"))
}
func getBuildURLHash() string {
return toSha1OrNA(os.Getenv("BUILD_URL"))
}
func toSha1OrNA(input string) string {
if len(input) == 0 {
return "n/a"
}
return fmt.Sprintf("%x", sha1.Sum([]byte(input)))
}
// SWA baseURL
const baseURL = "https://webanalytics.cfapps.eu10.hana.ondemand.com"
// SWA endpoint
const endpoint = "/tracker/log"
// Send ...
func Send(customData *CustomData) {
data := Data{
BaseData: baseData,
BaseMetaData: baseMetaData,
CustomData: *customData,
}
// skip if telemetry is dieabled
if disabled {
return
}
request, _ := url.Parse(baseURL)
request.Path = endpoint
request.RawQuery = data.toPayloadString()
log.Entry().WithField("request", request.String()).Debug("Sending telemetry data")
client.SendRequest(http.MethodGet, request.String(), nil, nil, nil)
}

View File

@ -0,0 +1,111 @@
package telemetry
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"os"
"testing"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/stretchr/testify/assert"
)
type clientMock struct {
httpMethod string
urlsCalled string
}
func (c *clientMock) SetOptions(opts piperhttp.ClientOptions) {}
func (c *clientMock) SendRequest(method, url string, body io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) {
c.httpMethod = method
c.urlsCalled = url
return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader([]byte("")))}, nil
}
var mock clientMock
func TestInitialise(t *testing.T) {
t.Run("with disabled telemetry", func(t *testing.T) {
// init
client = nil
// test
Initialize(true, "testStep")
// assert
assert.Equal(t, nil, client)
assert.Equal(t, BaseData{}, baseData)
})
t.Run("", func(t *testing.T) {
// init
client = nil
// test
Initialize(false, "testStep")
// assert
assert.NotEqual(t, nil, client)
assert.Equal(t, "testStep", baseData.StepName)
})
}
func TestSend(t *testing.T) {
t.Run("with disabled telemetry", func(t *testing.T) {
// init
mock = clientMock{}
client = &mock
disabled = true
// test
Send(&CustomData{})
// assert
assert.Equal(t, 0, len(mock.httpMethod))
assert.Equal(t, 0, len(mock.urlsCalled))
})
t.Run("", func(t *testing.T) {
// init
mock = clientMock{}
client = &mock
disabled = false
baseData = BaseData{
ActionName: "testAction",
}
// test
Send(&CustomData{
Custom1: "test",
Custom1Label: "label",
})
// assert
assert.Equal(t, "GET", mock.httpMethod)
assert.Contains(t, mock.urlsCalled, baseURL)
assert.Contains(t, mock.urlsCalled, "custom26=label")
assert.Contains(t, mock.urlsCalled, "e_26=test")
assert.Contains(t, mock.urlsCalled, "action_name=testAction")
})
}
func TestEnvVars(t *testing.T) {
t.Run("without values", func(t *testing.T) {
// init
client = nil
// test
Initialize(false, "testStep")
// assert
assert.Equal(t, "n/a", baseData.PipelineURLHash)
assert.Equal(t, "n/a", baseData.BuildURLHash)
})
t.Run("", func(t *testing.T) {
// init
os.Setenv("JOB_URL", "someValue")
os.Setenv("BUILD_URL", "someValue")
client = nil
// test
Initialize(false, "testStep")
// assert
assert.Equal(t, "c1353b55ce4db511684b8a3b7b5c4b3d99ee9dec", baseData.PipelineURLHash)
assert.Equal(t, "c1353b55ce4db511684b8a3b7b5c4b3d99ee9dec", baseData.BuildURLHash)
// cleanup
os.Unsetenv("JOB_URL")
os.Unsetenv("BUILD_URL")
})
}