-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
config.go
303 lines (265 loc) · 10.8 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package cloudapi
import (
"bytes"
"encoding/json"
"time"
"github.com/mstoykov/envconfig"
"go.k6.io/k6/lib/types"
"gopkg.in/guregu/null.v3"
)
// LegacyCloudConfigKey is the key used in the JSON config for the cloud output.
const LegacyCloudConfigKey = "loadimpact"
// Config holds all the necessary data and options for sending metrics to the k6 Cloud.
//
//nolint:lll
type Config struct {
// TODO: refactor common stuff between cloud execution and output
Token null.String `json:"token" envconfig:"K6_CLOUD_TOKEN"`
ProjectID null.Int `json:"projectID" envconfig:"K6_CLOUD_PROJECT_ID"`
Name null.String `json:"name" envconfig:"K6_CLOUD_NAME"`
Host null.String `json:"host" envconfig:"K6_CLOUD_HOST"`
Timeout types.NullDuration `json:"timeout" envconfig:"K6_CLOUD_TIMEOUT"`
LogsTailURL null.String `json:"-" envconfig:"K6_CLOUD_LOGS_TAIL_URL"`
WebAppURL null.String `json:"webAppURL" envconfig:"K6_CLOUD_WEB_APP_URL"`
TestRunDetails null.String `json:"testRunDetails" envconfig:"K6_CLOUD_TEST_RUN_DETAILS"`
NoCompress null.Bool `json:"noCompress" envconfig:"K6_CLOUD_NO_COMPRESS"`
StopOnError null.Bool `json:"stopOnError" envconfig:"K6_CLOUD_STOP_ON_ERROR"`
APIVersion null.Int `json:"apiVersion" envconfig:"K6_CLOUD_API_VERSION"`
// PushRefID represents the test run id.
// Note: It is a legacy name used by the backend, the code in k6 open-source
// references it as test run id.
// Currently, a renaming is not planned.
PushRefID null.String `json:"pushRefID" envconfig:"K6_CLOUD_PUSH_REF_ID"`
// Defines the max allowed number of time series in a single batch.
MaxTimeSeriesInBatch null.Int `json:"maxTimeSeriesInBatch" envconfig:"K6_CLOUD_MAX_TIME_SERIES_IN_BATCH"`
// The time interval between periodic API calls for sending samples to the cloud ingest service.
MetricPushInterval types.NullDuration `json:"metricPushInterval" envconfig:"K6_CLOUD_METRIC_PUSH_INTERVAL"`
// This is how many concurrent pushes will be done at the same time to the cloud
MetricPushConcurrency null.Int `json:"metricPushConcurrency" envconfig:"K6_CLOUD_METRIC_PUSH_CONCURRENCY"`
// If specified and is greater than 0, sample aggregation with that period is enabled
AggregationPeriod types.NullDuration `json:"aggregationPeriod" envconfig:"K6_CLOUD_AGGREGATION_PERIOD"`
// If aggregation is enabled, this specifies how long we'll wait for period samples to accumulate before trying to aggregate them.
AggregationWaitPeriod types.NullDuration `json:"aggregationWaitPeriod" envconfig:"K6_CLOUD_AGGREGATION_WAIT_PERIOD"`
// Indicates whether to send traces to the k6 Insights backend service.
TracesEnabled null.Bool `json:"tracesEnabled" envconfig:"K6_CLOUD_TRACES_ENABLED"`
// The host of the k6 Insights backend service.
TracesHost null.String `json:"traceHost" envconfig:"K6_CLOUD_TRACES_HOST"`
// This is how many concurrent pushes will be done at the same time to the cloud
TracesPushConcurrency null.Int `json:"tracesPushConcurrency" envconfig:"K6_CLOUD_TRACES_PUSH_CONCURRENCY"`
// The time interval between periodic API calls for sending samples to the cloud ingest service.
TracesPushInterval types.NullDuration `json:"tracesPushInterval" envconfig:"K6_CLOUD_TRACES_PUSH_INTERVAL"`
}
// NewConfig creates a new Config instance with default values for some fields.
func NewConfig() Config {
return Config{
APIVersion: null.NewInt(2, false),
Host: null.NewString("https://ingest.k6.io", false),
LogsTailURL: null.NewString("wss://cloudlogs.k6.io/api/v1/tail", false),
WebAppURL: null.NewString("https://app.k6.io", false),
MetricPushInterval: types.NewNullDuration(1*time.Second, false),
MetricPushConcurrency: null.NewInt(1, false),
Timeout: types.NewNullDuration(1*time.Minute, false),
// The set value (1000) is selected for performance reasons.
// Any change to this value should be first discussed with internal stakeholders.
MaxTimeSeriesInBatch: null.NewInt(1000, false),
// TODO: the following values were used by the previous default version (v1).
// We decided to keep the same values mostly for having a smoother migration to v2.
// Because the previous version's aggregation config, a few lines below, is overwritten
// by the remote service with the same values that we are now setting here for v2.
// When the migration will be completed we may evaluate to re-discuss them
// as we may evaluate to reduce these values - especially the waiting period.
// A more specific request about waiting period is mentioned in the link below:
// https://github.com/grafana/k6/blob/44e1e63aadb66784ff0a12b8d9821a0fdc9e7467/output/cloud/expv2/collect.go#L72-L77
AggregationPeriod: types.NewNullDuration(3*time.Second, false),
AggregationWaitPeriod: types.NewNullDuration(8*time.Second, false),
TracesEnabled: null.NewBool(true, false),
TracesHost: null.NewString("grpc-k6-api-prod-prod-us-east-0.grafana.net:443", false),
TracesPushInterval: types.NewNullDuration(1*time.Second, false),
TracesPushConcurrency: null.NewInt(1, false),
}
}
// Apply saves config non-zero config values from the passed config in the receiver.
//
//nolint:cyclop
func (c Config) Apply(cfg Config) Config {
if cfg.Token.Valid {
c.Token = cfg.Token
}
if cfg.ProjectID.Valid && cfg.ProjectID.Int64 > 0 {
c.ProjectID = cfg.ProjectID
}
if cfg.Name.Valid && cfg.Name.String != "" {
c.Name = cfg.Name
}
if cfg.Host.Valid && cfg.Host.String != "" {
c.Host = cfg.Host
}
if cfg.LogsTailURL.Valid && cfg.LogsTailURL.String != "" {
c.LogsTailURL = cfg.LogsTailURL
}
if cfg.PushRefID.Valid {
c.PushRefID = cfg.PushRefID
}
if cfg.WebAppURL.Valid {
c.WebAppURL = cfg.WebAppURL
}
if cfg.TestRunDetails.Valid {
c.TestRunDetails = cfg.TestRunDetails
}
if cfg.NoCompress.Valid {
c.NoCompress = cfg.NoCompress
}
if cfg.StopOnError.Valid {
c.StopOnError = cfg.StopOnError
}
if cfg.Timeout.Valid {
c.Timeout = cfg.Timeout
}
if cfg.APIVersion.Valid {
c.APIVersion = cfg.APIVersion
}
if cfg.MaxTimeSeriesInBatch.Valid {
c.MaxTimeSeriesInBatch = cfg.MaxTimeSeriesInBatch
}
if cfg.MetricPushInterval.Valid {
c.MetricPushInterval = cfg.MetricPushInterval
}
if cfg.MetricPushConcurrency.Valid {
c.MetricPushConcurrency = cfg.MetricPushConcurrency
}
if cfg.TracesEnabled.Valid {
c.TracesEnabled = cfg.TracesEnabled
}
if cfg.TracesHost.Valid {
c.TracesHost = cfg.TracesHost
}
if cfg.TracesPushInterval.Valid {
c.TracesPushInterval = cfg.TracesPushInterval
}
if cfg.TracesPushConcurrency.Valid {
c.TracesPushConcurrency = cfg.TracesPushConcurrency
}
if cfg.AggregationPeriod.Valid {
c.AggregationPeriod = cfg.AggregationPeriod
}
if cfg.AggregationWaitPeriod.Valid {
c.AggregationWaitPeriod = cfg.AggregationWaitPeriod
}
return c
}
// GetConsolidatedConfig combines the default config values with the JSON config
// values and environment variables and returns the final result.
// it also returns a warning message that could be shown to the user.
// to bring some attention to the fact that the user.
func GetConsolidatedConfig(
jsonRawConf json.RawMessage,
env map[string]string,
configArg string,
cloudConfig json.RawMessage,
external map[string]json.RawMessage,
) (Config, string, error) {
warn := ""
result := NewConfig()
if jsonRawConf != nil {
jsonConf := Config{}
if err := json.Unmarshal(jsonRawConf, &jsonConf); err != nil {
return result, warn, err
}
result = result.Apply(jsonConf)
}
if err := mergeFromCloudOptionAndExternal(cloudConfig, external, &result); err != nil {
return result, warn, err
}
// We want to show a warning if the user is using the only old way of defining the config.
// Note: Since the migration to the options.cloud is a long process, this warning is planned
// to be emitted for a long time (1-2 years), after some point, and depending on the state
// of migration we could re-evaluate this warning.
if cloudConfig == nil && external != nil {
if _, ok := external[LegacyCloudConfigKey]; ok {
warn = "The options.ext.loadimpact option is deprecated, please use options.cloud instead"
}
}
envConfig := Config{}
if err := envconfig.Process("", &envConfig, func(key string) (string, bool) {
v, ok := env[key]
return v, ok
}); err != nil {
// TODO: get rid of envconfig and actually use the env parameter...
return result, warn, err
}
result = result.Apply(envConfig)
if configArg != "" {
result.Name = null.StringFrom(configArg)
}
return result, warn, nil
}
// mergeFromCloudOptionAndExternal merges three fields from the JSON in a cloud key of
// the provided external map. Used for options.cloud settings.
func mergeFromCloudOptionAndExternal(
cloudConfig json.RawMessage,
external map[string]json.RawMessage,
conf *Config,
) error {
source := pickSource(cloudConfig, external)
if source == nil {
return nil
}
// Original comment
// TODO: Important! Separate configs and fix the whole 2 configs mess!
tmpConfig := Config{}
if err := json.Unmarshal(source, &tmpConfig); err != nil {
return err
}
// Only take out the ProjectID, Name and Token from the options.cloud (or legacy loadimpact struct) map:
if tmpConfig.ProjectID.Valid {
conf.ProjectID = tmpConfig.ProjectID
}
if tmpConfig.Name.Valid {
conf.Name = tmpConfig.Name
}
if tmpConfig.Token.Valid {
conf.Token = tmpConfig.Token
}
return nil
}
// GetTemporaryCloudConfig returns a temporary cloud config.
// Original comment
// TODO: Fix this
// We reuse cloud.Config for parsing options.cloud (or legacy loadimpact struct), but this probably shouldn't be
// done, as the idea of options.ext is that they are extensible without touching k6. But in
// order for this to happen, we shouldn't actually marshal cloud.Config on top of it, because
// it will be missing some fields that aren't actually mentioned in the struct.
// So in order for use to copy the fields that we need for k6 cloud's api we unmarshal in
// map[string]interface{} and copy what we need if it isn't set already
func GetTemporaryCloudConfig(
cloudConfig json.RawMessage,
external map[string]json.RawMessage,
) (map[string]interface{}, error) {
tmpCloudConfig := make(map[string]interface{}, 3)
source := pickSource(cloudConfig, external)
if source == nil {
return tmpCloudConfig, nil
}
dec := json.NewDecoder(bytes.NewReader(source))
dec.UseNumber() // otherwise float64 are used
if err := dec.Decode(&tmpCloudConfig); err != nil {
return nil, err
}
return tmpCloudConfig, nil
}
// pickSource returns the config source to use.
func pickSource(
cloudConfig json.RawMessage,
external map[string]json.RawMessage,
) json.RawMessage {
// priority is the new way of defining the config
// via options.cloud
if cloudConfig != nil {
return cloudConfig
}
// fallback to the old way of defining the config
if val, ok := external[LegacyCloudConfigKey]; ok {
return val
}
return nil
}