diff --git a/cmd/influxWriteData.go b/cmd/influxWriteData.go new file mode 100644 index 000000000..25ba1433f --- /dev/null +++ b/cmd/influxWriteData.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "encoding/json" + "fmt" + + "github.com/SAP/jenkins-library/pkg/influx" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/telemetry" + influxdb2 "github.com/influxdata/influxdb-client-go/v2" +) + +func influxWriteData(config influxWriteDataOptions, _ *telemetry.CustomData) { + influxClient := influxdb2.NewClient(config.ServerURL, config.AuthToken) + // Error situations should be bubbled up until they reach the line below which will then stop execution + // through the log.Entry().Fatal() call leading to an os.Exit(1) in the end. + err := writeData(&config, influxClient) + if err != nil { + log.Entry().WithError(err).Fatal("step execution failed") + } +} + +func writeData(config *influxWriteDataOptions, influxClient influxdb2.Client) error { + log.Entry().Info("influxWriteData step") + + client := influx.NewClient(influxClient, config.Organization, config.Bucket) + var dataMap map[string]map[string]interface{} + if err := json.Unmarshal([]byte(config.DataMap), &dataMap); err != nil { + return fmt.Errorf("Failed to unmarshal dataMap: %v", err) + } + var dataMapTags map[string]map[string]string + if config.DataMapTags != "" { + if err := json.Unmarshal([]byte(config.DataMapTags), &dataMapTags); err != nil { + return fmt.Errorf("Failed to unmarshal dataMapTags: %v", err) + } + } + if err := client.WriteMetrics(dataMap, dataMapTags); err != nil { + return err + } + log.Entry().Info("Metrics have been written successfully") + return nil +} diff --git a/cmd/influxWriteData_generated.go b/cmd/influxWriteData_generated.go new file mode 100644 index 000000000..79956cc5a --- /dev/null +++ b/cmd/influxWriteData_generated.go @@ -0,0 +1,196 @@ +// Code generated by piper's step-generator. DO NOT EDIT. + +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/SAP/jenkins-library/pkg/config" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/splunk" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/spf13/cobra" +) + +type influxWriteDataOptions struct { + ServerURL string `json:"serverUrl,omitempty"` + AuthToken string `json:"authToken,omitempty"` + Bucket string `json:"bucket,omitempty"` + Organization string `json:"organization,omitempty"` + DataMap string `json:"dataMap,omitempty"` + DataMapTags string `json:"dataMapTags,omitempty"` +} + +// InfluxWriteDataCommand Writes metrics to influxdb +func InfluxWriteDataCommand() *cobra.Command { + const STEP_NAME = "influxWriteData" + + metadata := influxWriteDataMetadata() + var stepConfig influxWriteDataOptions + var startTime time.Time + var logCollector *log.CollectorHook + + var createInfluxWriteDataCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "Writes metrics to influxdb", + Long: `In this step, the metrics are written to the timeseries database [InfluxDB](https://www.influxdata.com/time-series-platform/influxdb/)`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + startTime = time.Now() + log.SetStepName(STEP_NAME) + log.SetVerbose(GeneralConfig.Verbose) + + path, _ := os.Getwd() + fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} + log.RegisterHook(fatalHook) + + err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile) + if err != nil { + log.SetErrorCategory(log.ErrorConfiguration) + return err + } + log.RegisterSecret(stepConfig.AuthToken) + + if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 { + sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID) + log.RegisterHook(&sentryHook) + } + + if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { + logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID} + log.RegisterHook(logCollector) + } + + return nil + }, + Run: func(_ *cobra.Command, _ []string) { + telemetryData := telemetry.CustomData{} + telemetryData.ErrorCode = "1" + handler := func() { + config.RemoveVaultSecretFiles() + telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) + telemetryData.ErrorCategory = log.GetErrorCategory().String() + telemetry.Send(&telemetryData) + if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { + splunk.Send(&telemetryData, logCollector) + } + } + log.DeferExitHandler(handler) + defer handler() + telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) + if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { + splunk.Initialize(GeneralConfig.CorrelationID, + GeneralConfig.HookConfig.SplunkConfig.Dsn, + GeneralConfig.HookConfig.SplunkConfig.Token, + GeneralConfig.HookConfig.SplunkConfig.Index, + GeneralConfig.HookConfig.SplunkConfig.SendLogs) + } + influxWriteData(stepConfig, &telemetryData) + telemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addInfluxWriteDataFlags(createInfluxWriteDataCmd, &stepConfig) + return createInfluxWriteDataCmd +} + +func addInfluxWriteDataFlags(cmd *cobra.Command, stepConfig *influxWriteDataOptions) { + cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", os.Getenv("PIPER_serverUrl"), "Base URL to the InfluxDB server") + cmd.Flags().StringVar(&stepConfig.AuthToken, "authToken", os.Getenv("PIPER_authToken"), "Token to authenticate to the Influxdb") + cmd.Flags().StringVar(&stepConfig.Bucket, "bucket", `piper`, "Name of database (1.8) or bucket (2.0)") + cmd.Flags().StringVar(&stepConfig.Organization, "organization", os.Getenv("PIPER_organization"), "Name of influx organization. Only for Influxdb 2.0") + cmd.Flags().StringVar(&stepConfig.DataMap, "dataMap", os.Getenv("PIPER_dataMap"), "Map of fields for each measurements. It has to be a JSON string. For example: {'series_1':{'field_a':11,'field_b':12},'series_2':{'field_c':21,'field_d':22}}") + cmd.Flags().StringVar(&stepConfig.DataMapTags, "dataMapTags", os.Getenv("PIPER_dataMapTags"), "Map of tags for each measurements. It has to be a JSON string. For example: {'series_1':{'tag_a':'a','tag_b':'b'},'series_2':{'tag_c':'c','tag_d':'d'}}") + + cmd.MarkFlagRequired("serverUrl") + cmd.MarkFlagRequired("authToken") + cmd.MarkFlagRequired("dataMap") +} + +// retrieve step metadata +func influxWriteDataMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "influxWriteData", + Aliases: []config.Alias{}, + Description: "Writes metrics to influxdb", + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Secrets: []config.StepSecrets{ + {Name: "influxAuthTokenId", Description: "Influxdb token for authentication to the InfluxDB. In 1.8 version use 'username:password' instead.", Type: "jenkins"}, + }, + Parameters: []config.StepParameters{ + { + Name: "serverUrl", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_serverUrl"), + }, + { + Name: "authToken", + ResourceRef: []config.ResourceReference{ + { + Name: "influxAuthTokenId", + Type: "secret", + }, + + { + Name: "", + Paths: []string{"$(vaultPath)/influxdb", "$(vaultBasePath)/$(vaultPipelineName)/influxdb", "$(vaultBasePath)/GROUP-SECRETS/influxdb"}, + Type: "vaultSecret", + }, + }, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_authToken"), + }, + { + Name: "bucket", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: `piper`, + }, + { + Name: "organization", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_organization"), + }, + { + Name: "dataMap", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_dataMap"), + }, + { + Name: "dataMapTags", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_dataMapTags"), + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/influxWriteData_generated_test.go b/cmd/influxWriteData_generated_test.go new file mode 100644 index 000000000..03a486c8d --- /dev/null +++ b/cmd/influxWriteData_generated_test.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInfluxWriteDataCommand(t *testing.T) { + t.Parallel() + + testCmd := InfluxWriteDataCommand() + + // only high level testing performed - details are tested in step generation procedure + assert.Equal(t, "influxWriteData", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/influxWriteData_test.go b/cmd/influxWriteData_test.go new file mode 100644 index 000000000..4fafccfaf --- /dev/null +++ b/cmd/influxWriteData_test.go @@ -0,0 +1,77 @@ +package cmd + +import ( + "errors" + "testing" + + "github.com/SAP/jenkins-library/pkg/influx/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestWriteData(t *testing.T) { + options := &influxWriteDataOptions{ + ServerURL: "http://localhost:8086", + AuthToken: "authToken", + Bucket: "piper", + Organization: "org", + } + errString := "some error" + errWriteData := errors.New(errString) + tests := []struct { + name string + dataMap string + dataMapTags string + writeDataErr error + errExpected bool + errIncludeStr string + }{ + { + "Test writing metrics with correct json data - success", + `{"series_1": {"field_a": 11, "field_b": 12}, "series_2": {"field_c": 21, "field_d": 22}}`, + `{"series_1": {"tag_a": "a", "tag_b": "b"}, "series_2": {"tag_c": "c", "tag_d": "d"}}`, + nil, + false, + "", + }, + { + "Test writing metrics with invalid dataMap", + `"series_1": {"field_a": 11, "field_b": 12}, "series_2": {"field_c": 21, "field_d": 22}`, + `{"series_1": {"tag_a": "a", "tag_b": "b"}, "series_2": {"tag_c": "c", "tag_d": "d"}}`, + nil, + false, + "Failed to unmarshal dataMap:", + }, + { + "Test writing metrics with invalid dataMapTags", + `{"series_1": {"field_a": 11, "field_b": 12}, "series_2": {"field_c": 21, "field_d": 22}}`, + `{"series_1": {"tag_a": 2, "tag_b": "b"}, "series_2": {"tag_c": "c", "tag_d": "d"}}`, + nil, + false, + "Failed to unmarshal dataMapTags:", + }, + { + "Test writing metrics with correct json data - failed", + `{"series_1": {"field_a": 11, "field_b": 12}, "series_2": {"field_c": 21, "field_d": 22}}`, + `{"series_1": {"tag_a": "a", "tag_b": "b"}, "series_2": {"tag_c": "c", "tag_d": "d"}}`, + errWriteData, + true, + errString, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + influxClientMock := &mocks.Client{} + writeAPIBlockingMock := &mocks.WriteAPIBlocking{} + writeAPIBlockingMock.On("WritePoint", mock.Anything, mock.Anything).Return(tt.writeDataErr) + influxClientMock.On("WriteAPIBlocking", mock.Anything, mock.Anything).Return(writeAPIBlockingMock) + options.DataMap = tt.dataMap + options.DataMapTags = tt.dataMapTags + err := writeData(options, influxClientMock) + if err != nil { + assert.Contains(t, err.Error(), tt.errIncludeStr) + } + }) + } +} diff --git a/cmd/metadata_generated.go b/cmd/metadata_generated.go index bd9aeafe1..4cacc7831 100644 --- a/cmd/metadata_generated.go +++ b/cmd/metadata_generated.go @@ -47,6 +47,7 @@ func GetAllStepMetadata() map[string]config.StepData { "githubSetCommitStatus": githubSetCommitStatusMetadata(), "gitopsUpdateDeployment": gitopsUpdateDeploymentMetadata(), "hadolintExecute": hadolintExecuteMetadata(), + "influxWriteData": influxWriteDataMetadata(), "integrationArtifactDeploy": integrationArtifactDeployMetadata(), "integrationArtifactDownload": integrationArtifactDownloadMetadata(), "integrationArtifactGetMplStatus": integrationArtifactGetMplStatusMetadata(), diff --git a/cmd/piper.go b/cmd/piper.go index 3d5e93cd2..35e283c30 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -156,6 +156,7 @@ func Execute() { rootCmd.AddCommand(TransportRequestReqIDFromGitCommand()) rootCmd.AddCommand(WritePipelineEnv()) rootCmd.AddCommand(ReadPipelineEnv()) + rootCmd.AddCommand(InfluxWriteDataCommand()) addRootFlags(rootCmd) if err := rootCmd.Execute(); err != nil { diff --git a/go.mod b/go.mod index 2d810b89b..6ef85c356 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/hashicorp/vault/api v1.1.0 github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.11 // indirect + github.com/influxdata/influxdb-client-go/v2 v2.4.0 github.com/jarcoal/httpmock v1.0.8 github.com/magiconair/properties v1.8.4 github.com/magicsong/color-glog v0.0.1 // indirect diff --git a/go.sum b/go.sum index 7d199fd53..124fabb71 100644 --- a/go.sum +++ b/go.sum @@ -327,10 +327,13 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 h1:lrWnAyy/F72MbxIxFUzKmcMCdt9Oi8RzpAxzTNQHD7o= @@ -428,6 +431,7 @@ github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7/go.mod h1:GeIq9qoE github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56 h1:VzbudKn/nvxYKOdzgkEBS6SSreRjAgoJ+ZeS4wPFkgc= github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56/go.mod h1:w9RqFVO2BM3xwWEcAB8Fwp0OviTBBEiRmSBDfbXnd3w= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getsentry/sentry-go v0.7.0 h1:MR2yfR4vFfv/2+iBuSnkdQwVg7N9cJzihZ6KJu7srwQ= github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -447,6 +451,7 @@ github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkPro github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= @@ -499,8 +504,9 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+ github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= @@ -651,6 +657,7 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= @@ -715,8 +722,9 @@ github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b0 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= @@ -949,7 +957,11 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4 h1:3K3KcD4S6/Y2hevi70EzUTNKOS3cryQyhUnkjE6Tz0w= github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= @@ -1054,6 +1066,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= @@ -1087,6 +1100,7 @@ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsI github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1464,6 +1478,7 @@ github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKn github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= @@ -1660,8 +1675,9 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c/go.mod h1:iQL9McJNjoIa5mjH6nYTCTZXUN6RP+XW3eib7Ya3XcI= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1686,8 +1702,9 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cO golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1702,8 +1719,10 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/integration/integration_influx_test.go b/integration/integration_influx_test.go new file mode 100644 index 000000000..4d47ce641 --- /dev/null +++ b/integration/integration_influx_test.go @@ -0,0 +1,92 @@ +// +build integration +// can be execute with go test -tags=integration ./integration/... + +package main + +import ( + "context" + "fmt" + "testing" + + "github.com/SAP/jenkins-library/pkg/influx" + influxdb2 "github.com/influxdata/influxdb-client-go/v2" + "github.com/stretchr/testify/assert" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +func TestWriteMetrics(t *testing.T) { + t.Parallel() + ctx := context.Background() + const authToken = "influx-token" + const username = "username" + const password = "password" + const bucket = "piper" + const organization = "org" + + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + AlwaysPullImage: true, + Image: "influxdb:2.0", + ExposedPorts: []string{"8086/tcp"}, + Env: map[string]string{ + "DOCKER_INFLUXDB_INIT_MODE": "setup", + "DOCKER_INFLUXDB_INIT_USERNAME": username, + "DOCKER_INFLUXDB_INIT_PASSWORD": password, + "DOCKER_INFLUXDB_INIT_ORG": organization, + "DOCKER_INFLUXDB_INIT_BUCKET": bucket, + "DOCKER_INFLUXDB_INIT_ADMIN_TOKEN": authToken, + }, + WaitingFor: wait.ForListeningPort("8086/tcp"), + }, + Started: true, + } + + influxContainer, err := testcontainers.GenericContainer(ctx, req) + assert.NoError(t, err) + defer influxContainer.Terminate(ctx) + + ip, err := influxContainer.Host(ctx) + assert.NoError(t, err) + port, err := influxContainer.MappedPort(ctx, "8086") + host := fmt.Sprintf("http://%s:%s", ip, port.Port()) + dataMap := map[string]map[string]interface{}{ + "series_1": {"field_a": 11, "field_b": 12}, + "series_2": {"field_c": 21, "field_d": 22}, + } + dataMapTags := map[string]map[string]string{ + "series_1": {"tag_a": "a", "tag_b": "b"}, + "series_2": {"tag_c": "c", "tag_d": "d"}, + } + influxClient := influxdb2.NewClient(host, authToken) + defer influxClient.Close() + client := influx.NewClient(influxClient, organization, bucket) + err = client.WriteMetrics(dataMap, dataMapTags) + assert.NoError(t, err) + + queryAPI := influxClient.QueryAPI(organization) + result, err := queryAPI.Query(context.Background(), + `from(bucket:"piper")|> range(start: -1h) |> filter(fn: (r) => r._measurement == "series_1" or r._measurement == "series_2")`) + assert.NoError(t, err) + valuesMap := map[string]map[string]interface{}{} + expectedValuesMap := map[string]map[string]interface{}{ + "series_1_field_a": {"_field": "field_a", "_measurement": "series_1", "_value": int64(11), "tag_a": "a", "tag_b": "b"}, + "series_1_field_b": {"_field": "field_b", "_measurement": "series_1", "_value": int64(12), "tag_a": "a", "tag_b": "b"}, + "series_2_field_c": {"_field": "field_c", "_measurement": "series_2", "_value": int64(21), "tag_c": "c", "tag_d": "d"}, + "series_2_field_d": {"_field": "field_d", "_measurement": "series_2", "_value": int64(22), "tag_c": "c", "tag_d": "d"}, + } + for result.Next() { + values := result.Record().Values() + measurement := values["_measurement"] + field := values["_field"] + delete(values, "_start") + delete(values, "_stop") + delete(values, "_time") + delete(values, "result") + delete(values, "table") + valuesMap[fmt.Sprintf("%v_%v", measurement, field)] = values + } + assert.NoError(t, result.Err()) + assert.Equal(t, len(expectedValuesMap), len(valuesMap)) + assert.Equal(t, expectedValuesMap, valuesMap) +} diff --git a/pkg/influx/client.go b/pkg/influx/client.go new file mode 100644 index 000000000..c48a413f1 --- /dev/null +++ b/pkg/influx/client.go @@ -0,0 +1,45 @@ +package influx + +import ( + "context" + "time" + + influxdb2 "github.com/influxdata/influxdb-client-go/v2" +) + +// Client handles communication with InfluxDB +type Client struct { + client influxdb2.Client + ctx context.Context + organization string + bucket string +} + +// NewClient instantiates a Client +func NewClient(influxClient influxdb2.Client, organization string, bucket string) *Client { + ctx := context.Background() + client := Client{ + client: influxClient, + ctx: ctx, + organization: organization, + bucket: bucket, + } + return &client +} + +// WriteMetrics writes metrics to InfluxDB +func (c *Client) WriteMetrics(dataMap map[string]map[string]interface{}, dataMapTags map[string]map[string]string) error { + writeAPI := c.client.WriteAPIBlocking(c.organization, c.bucket) + + for measurement, fields := range dataMap { + tags := dataMapTags[measurement] + point := influxdb2.NewPoint(measurement, + tags, + fields, + time.Now()) + if err := writeAPI.WritePoint(c.ctx, point); err != nil { + return err + } + } + return nil +} diff --git a/pkg/influx/client_test.go b/pkg/influx/client_test.go new file mode 100644 index 000000000..5fb46406e --- /dev/null +++ b/pkg/influx/client_test.go @@ -0,0 +1,62 @@ +package influx + +import ( + "errors" + "testing" + + "github.com/SAP/jenkins-library/pkg/influx/mocks" + "github.com/stretchr/testify/mock" +) + +func TestWriteMetrics(t *testing.T) { + errWriteMetrics := errors.New("error") + tests := []struct { + name string + dataMap map[string]map[string]interface{} + dataMapTags map[string]map[string]string + writePointErr error + err error + }{ + { + "Test writing metrics - success", + map[string]map[string]interface{}{ + "series_1": {"field_a": 11, "field_b": 12}, + "series_2": {"field_c": 21, "field_d": 22}, + }, + map[string]map[string]string{ + "series_1": {"tag_a": "a", "tag_b": "b"}, + "series_2": {"tag_c": "c", "tag_d": "d"}, + }, + nil, + nil, + }, + { + "Test writing metrics - failed", + map[string]map[string]interface{}{ + "series_1": {"field_a": 11, "field_b": 12}, + "series_2": {"field_c": 21, "field_d": 22}, + }, + map[string]map[string]string{ + "series_1": {"tag_a": "a", "tag_b": "b"}, + "series_2": {"tag_c": "c", "tag_d": "d"}, + }, + errWriteMetrics, + errWriteMetrics, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + influxClientMock := &mocks.Client{} + client := NewClient(influxClientMock, "org", "piper") + writeAPIBlockingMock := &mocks.WriteAPIBlocking{} + writeAPIBlockingMock.On("WritePoint", client.ctx, mock.Anything).Return(tt.writePointErr) + influxClientMock.On("WriteAPIBlocking", client.organization, client.bucket).Return(writeAPIBlockingMock) + err := client.WriteMetrics(tt.dataMap, tt.dataMapTags) + if err != tt.err { + t.Errorf("\nactual: %q\nexpected: %q\n", err, tt.err) + } + }) + } + +} diff --git a/pkg/influx/mocks/client.go b/pkg/influx/mocks/client.go new file mode 100644 index 000000000..969e2cbaf --- /dev/null +++ b/pkg/influx/mocks/client.go @@ -0,0 +1,300 @@ +// Code generated by mockery v2.7.4. DO NOT EDIT. + +package mocks + +import ( + context "context" + + api "github.com/influxdata/influxdb-client-go/v2/api" + + domain "github.com/influxdata/influxdb-client-go/v2/domain" + + http "github.com/influxdata/influxdb-client-go/v2/api/http" + + influxdb2 "github.com/influxdata/influxdb-client-go/v2" + + mock "github.com/stretchr/testify/mock" +) + +// Client is an autogenerated mock type for the Client type +type Client struct { + mock.Mock +} + +// AuthorizationsAPI provides a mock function with given fields: +func (_m *Client) AuthorizationsAPI() api.AuthorizationsAPI { + ret := _m.Called() + + var r0 api.AuthorizationsAPI + if rf, ok := ret.Get(0).(func() api.AuthorizationsAPI); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.AuthorizationsAPI) + } + } + + return r0 +} + +// BucketsAPI provides a mock function with given fields: +func (_m *Client) BucketsAPI() api.BucketsAPI { + ret := _m.Called() + + var r0 api.BucketsAPI + if rf, ok := ret.Get(0).(func() api.BucketsAPI); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.BucketsAPI) + } + } + + return r0 +} + +// Close provides a mock function with given fields: +func (_m *Client) Close() { + _m.Called() +} + +// DeleteAPI provides a mock function with given fields: +func (_m *Client) DeleteAPI() api.DeleteAPI { + ret := _m.Called() + + var r0 api.DeleteAPI + if rf, ok := ret.Get(0).(func() api.DeleteAPI); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.DeleteAPI) + } + } + + return r0 +} + +// HTTPService provides a mock function with given fields: +func (_m *Client) HTTPService() http.Service { + ret := _m.Called() + + var r0 http.Service + if rf, ok := ret.Get(0).(func() http.Service); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(http.Service) + } + } + + return r0 +} + +// Health provides a mock function with given fields: ctx +func (_m *Client) Health(ctx context.Context) (*domain.HealthCheck, error) { + ret := _m.Called(ctx) + + var r0 *domain.HealthCheck + if rf, ok := ret.Get(0).(func(context.Context) *domain.HealthCheck); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.HealthCheck) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// LabelsAPI provides a mock function with given fields: +func (_m *Client) LabelsAPI() api.LabelsAPI { + ret := _m.Called() + + var r0 api.LabelsAPI + if rf, ok := ret.Get(0).(func() api.LabelsAPI); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.LabelsAPI) + } + } + + return r0 +} + +// Options provides a mock function with given fields: +func (_m *Client) Options() *influxdb2.Options { + ret := _m.Called() + + var r0 *influxdb2.Options + if rf, ok := ret.Get(0).(func() *influxdb2.Options); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*influxdb2.Options) + } + } + + return r0 +} + +// OrganizationsAPI provides a mock function with given fields: +func (_m *Client) OrganizationsAPI() api.OrganizationsAPI { + ret := _m.Called() + + var r0 api.OrganizationsAPI + if rf, ok := ret.Get(0).(func() api.OrganizationsAPI); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.OrganizationsAPI) + } + } + + return r0 +} + +// QueryAPI provides a mock function with given fields: org +func (_m *Client) QueryAPI(org string) api.QueryAPI { + ret := _m.Called(org) + + var r0 api.QueryAPI + if rf, ok := ret.Get(0).(func(string) api.QueryAPI); ok { + r0 = rf(org) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.QueryAPI) + } + } + + return r0 +} + +// Ready provides a mock function with given fields: ctx +func (_m *Client) Ready(ctx context.Context) (bool, error) { + ret := _m.Called(ctx) + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ServerURL provides a mock function with given fields: +func (_m *Client) ServerURL() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Setup provides a mock function with given fields: ctx, username, password, org, bucket, retentionPeriodHours +func (_m *Client) Setup(ctx context.Context, username string, password string, org string, bucket string, retentionPeriodHours int) (*domain.OnboardingResponse, error) { + ret := _m.Called(ctx, username, password, org, bucket, retentionPeriodHours) + + var r0 *domain.OnboardingResponse + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, int) *domain.OnboardingResponse); ok { + r0 = rf(ctx, username, password, org, bucket, retentionPeriodHours) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.OnboardingResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, int) error); ok { + r1 = rf(ctx, username, password, org, bucket, retentionPeriodHours) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TasksAPI provides a mock function with given fields: +func (_m *Client) TasksAPI() api.TasksAPI { + ret := _m.Called() + + var r0 api.TasksAPI + if rf, ok := ret.Get(0).(func() api.TasksAPI); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.TasksAPI) + } + } + + return r0 +} + +// UsersAPI provides a mock function with given fields: +func (_m *Client) UsersAPI() api.UsersAPI { + ret := _m.Called() + + var r0 api.UsersAPI + if rf, ok := ret.Get(0).(func() api.UsersAPI); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.UsersAPI) + } + } + + return r0 +} + +// WriteAPI provides a mock function with given fields: org, bucket +func (_m *Client) WriteAPI(org string, bucket string) api.WriteAPI { + ret := _m.Called(org, bucket) + + var r0 api.WriteAPI + if rf, ok := ret.Get(0).(func(string, string) api.WriteAPI); ok { + r0 = rf(org, bucket) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.WriteAPI) + } + } + + return r0 +} + +// WriteAPIBlocking provides a mock function with given fields: org, bucket +func (_m *Client) WriteAPIBlocking(org string, bucket string) api.WriteAPIBlocking { + ret := _m.Called(org, bucket) + + var r0 api.WriteAPIBlocking + if rf, ok := ret.Get(0).(func(string, string) api.WriteAPIBlocking); ok { + r0 = rf(org, bucket) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(api.WriteAPIBlocking) + } + } + + return r0 +} diff --git a/pkg/influx/mocks/writeAPIBlocking.go b/pkg/influx/mocks/writeAPIBlocking.go new file mode 100644 index 000000000..d95c53f81 --- /dev/null +++ b/pkg/influx/mocks/writeAPIBlocking.go @@ -0,0 +1,57 @@ +// Code generated by mockery v2.7.4. DO NOT EDIT. + +package mocks + +import ( + context "context" + + write "github.com/influxdata/influxdb-client-go/v2/api/write" + mock "github.com/stretchr/testify/mock" +) + +// WriteAPIBlocking is an autogenerated mock type for the WriteAPIBlocking type +type WriteAPIBlocking struct { + mock.Mock +} + +// WritePoint provides a mock function with given fields: ctx, point +func (_m *WriteAPIBlocking) WritePoint(ctx context.Context, point ...*write.Point) error { + _va := make([]interface{}, len(point)) + for _i := range point { + _va[_i] = point[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ...*write.Point) error); ok { + r0 = rf(ctx, point...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// WriteRecord provides a mock function with given fields: ctx, line +func (_m *WriteAPIBlocking) WriteRecord(ctx context.Context, line ...string) error { + _va := make([]interface{}, len(line)) + for _i := range line { + _va[_i] = line[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ...string) error); ok { + r0 = rf(ctx, line...) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/resources/metadata/influx.yaml b/resources/metadata/influx.yaml new file mode 100644 index 000000000..20fb04955 --- /dev/null +++ b/resources/metadata/influx.yaml @@ -0,0 +1,73 @@ +metadata: + name: influxWriteData + description: Writes metrics to influxdb + longDescription: | + In this step, the metrics are written to the timeseries database [InfluxDB](https://www.influxdata.com/time-series-platform/influxdb/) +spec: + inputs: + secrets: + - name: influxAuthTokenId + description: Influxdb token for authentication to the InfluxDB. In 1.8 version use 'username:password' instead. + type: jenkins + params: + - name: serverUrl + type: string + description: Base URL to the InfluxDB server + scope: + - PARAMETERS + - STAGES + - STEPS + - GENERAL + mandatory: true + - name: authToken + type: string + description: Token to authenticate to the Influxdb + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + secret: true + resourceRef: + - name: influxAuthTokenId + type: secret + - type: vaultSecret + paths: + - $(vaultPath)/influxdb + - $(vaultBasePath)/$(vaultPipelineName)/influxdb + - $(vaultBasePath)/GROUP-SECRETS/influxdb + - name: bucket + type: string + description: Name of database (1.8) or bucket (2.0) + scope: + - PARAMETERS + - STAGES + - STEPS + default: "piper" + - name: organization + type: string + description: Name of influx organization. Only for Influxdb 2.0 + scope: + - PARAMETERS + - STAGES + - STEPS + - name: dataMap + type: string + description: + "Map of fields for each measurements. It has to be a JSON string. + For example: {'series_1':{'field_a':11,'field_b':12},'series_2':{'field_c':21,'field_d':22}}" + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + - name: dataMapTags + type: string + description: + "Map of tags for each measurements. It has to be a JSON string. + For example: {'series_1':{'tag_a':'a','tag_b':'b'},'series_2':{'tag_c':'c','tag_d':'d'}}" + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false