@@ -3,15 +3,19 @@ package webhook
3
3
import (
4
4
"bytes"
5
5
"context"
6
+ "crypto/sha256"
6
7
"encoding/json"
7
8
"fmt"
8
9
"io"
9
10
"net/http"
11
+ "regexp"
12
+ "sort"
10
13
"text/template"
11
14
12
15
"github.com/pkg/errors"
13
16
"go.uber.org/zap"
14
17
18
+ executorv1 "github.com/kubeshop/testkube-operator/api/executor/v1"
15
19
"github.com/kubeshop/testkube/cmd/api-server/commons"
16
20
v1 "github.com/kubeshop/testkube/internal/app/api/metrics"
17
21
"github.com/kubeshop/testkube/internal/config"
@@ -21,6 +25,7 @@ import (
21
25
thttp "github.com/kubeshop/testkube/pkg/http"
22
26
"github.com/kubeshop/testkube/pkg/log"
23
27
"github.com/kubeshop/testkube/pkg/repository/testworkflow"
28
+ "github.com/kubeshop/testkube/pkg/secret"
24
29
"github.com/kubeshop/testkube/pkg/utils"
25
30
"github.com/kubeshop/testkube/pkg/utils/text"
26
31
)
@@ -33,9 +38,11 @@ func NewWebhookListener(name, uri, selector string, events []testkube.EventType,
33
38
testWorkflowExecutionResults testworkflow.Repository ,
34
39
metrics v1.Metrics ,
35
40
webhookRepository cloudwebhook.WebhookRepository ,
41
+ secretClient secret.Interface ,
36
42
proContext * config.ProContext ,
37
43
envs map [string ]string ,
38
- config map [string ]string ,
44
+ config map [string ]executorv1.WebhookConfigValue ,
45
+ parameters []executorv1.WebhookParameterSchema ,
39
46
) * WebhookListener {
40
47
return & WebhookListener {
41
48
name : name ,
@@ -52,9 +59,11 @@ func NewWebhookListener(name, uri, selector string, events []testkube.EventType,
52
59
testWorkflowExecutionResults : testWorkflowExecutionResults ,
53
60
metrics : metrics ,
54
61
webhookRepository : webhookRepository ,
62
+ secretClient : secretClient ,
55
63
proContext : proContext ,
56
64
envs : envs ,
57
65
config : config ,
66
+ parameters : parameters ,
58
67
}
59
68
}
60
69
@@ -73,9 +82,11 @@ type WebhookListener struct {
73
82
testWorkflowExecutionResults testworkflow.Repository
74
83
metrics v1.Metrics
75
84
webhookRepository cloudwebhook.WebhookRepository
85
+ secretClient secret.Interface
76
86
proContext * config.ProContext
77
87
envs map [string ]string
78
- config map [string ]string
88
+ config map [string ]executorv1.WebhookConfigValue
89
+ parameters []executorv1.WebhookParameterSchema
79
90
}
80
91
81
92
func (l * WebhookListener ) Name () string {
@@ -90,15 +101,32 @@ func (l *WebhookListener) Events() []testkube.EventType {
90
101
return l .events
91
102
}
92
103
func (l * WebhookListener ) Metadata () map [string ]string {
104
+ headers , err := getMapHashedMetadata (l .headers )
105
+ if err != nil {
106
+ l .Log .Errorw ("headers hashing error" , "error" , err )
107
+ }
108
+
109
+ config , err := getMapHashedMetadata (l .config )
110
+ if err != nil {
111
+ l .Log .Errorw ("config hashing error" , "error" , err )
112
+ }
113
+
114
+ parameters , err := getSliceHashedMetadata (l .parameters )
115
+ if err != nil {
116
+ l .Log .Errorw ("parameters hashing error" , "error" , err )
117
+ }
118
+
93
119
return map [string ]string {
94
120
"name" : l .Name (),
95
121
"uri" : l .Uri ,
96
122
"selector" : l .selector ,
97
123
"events" : fmt .Sprintf ("%v" , l .events ),
98
124
"payloadObjectField" : l .payloadObjectField ,
99
- "payloadTemplate" : l .payloadTemplate ,
100
- "headers" : fmt . Sprintf ( "%v" , l . headers ) ,
125
+ "payloadTemplate" : getTextHashedMetadata ([] byte ( l .payloadTemplate )) ,
126
+ "headers" : headers ,
101
127
"disabled" : fmt .Sprint (l .disabled ),
128
+ "config" : config ,
129
+ "parameters" : parameters ,
102
130
}
103
131
}
104
132
@@ -290,8 +318,62 @@ func (l *WebhookListener) processTemplate(field, body string, event testkube.Eve
290
318
return nil , err
291
319
}
292
320
321
+ config := make (map [string ]string )
322
+ for key , val := range l .config {
323
+ var data string
324
+ if val .Value != nil {
325
+ data = * val .Value
326
+ }
327
+
328
+ if val .Secret != nil {
329
+ var ns []string
330
+ if val .Secret .Namespace != "" {
331
+ ns = append (ns , val .Secret .Namespace )
332
+ }
333
+
334
+ elements , err := l .secretClient .Get (val .Secret .Name , ns ... )
335
+ if err != nil {
336
+ log .Errorw ("error secret loading" , "error" , err , "name" , val .Secret .Name )
337
+ return nil , err
338
+ }
339
+
340
+ if element , ok := elements [val .Secret .Key ]; ok {
341
+ data = element
342
+ } else {
343
+ log .Errorw ("error secret key finding loading" , "name" , val .Secret .Name , "key" , val .Secret .Key )
344
+ return nil , errors .New ("error secret key finding loading" )
345
+ }
346
+ }
347
+
348
+ config [key ] = data
349
+ }
350
+
351
+ for _ , parameter := range l .parameters {
352
+ if _ , ok := config [parameter .Name ]; ! ok {
353
+ if parameter .Default_ != nil {
354
+ config [parameter .Name ] = * parameter .Default_
355
+ } else if parameter .Required {
356
+ log .Errorw ("error missing required parameter" , "name" , parameter .Name )
357
+ return nil , errors .New ("error missing required parameter" )
358
+ }
359
+ }
360
+
361
+ if parameter .Pattern != "" {
362
+ re , err := regexp .Compile (parameter .Pattern )
363
+ if err != nil {
364
+ log .Errorw ("error compiling pattern" , "error" , err , "name" , parameter .Name , "pattern" , parameter .Pattern )
365
+ return nil , err
366
+ }
367
+
368
+ if data , ok := config [parameter .Name ]; ok && ! re .MatchString (data ) {
369
+ log .Errorw ("error matching pattern" , "error" , err , "name" , parameter .Name , "pattern" , parameter .Pattern )
370
+ return nil , errors .New ("error matching pattern" )
371
+ }
372
+ }
373
+ }
374
+
293
375
var buffer bytes.Buffer
294
- if err = tmpl .ExecuteTemplate (& buffer , field , NewTemplateVars (event , l .proContext , l . config )); err != nil {
376
+ if err = tmpl .ExecuteTemplate (& buffer , field , NewTemplateVars (event , l .proContext , config )); err != nil {
295
377
log .Errorw (fmt .Sprintf ("executing webhook %s error" , field ), "error" , err )
296
378
return nil , err
297
379
}
@@ -346,3 +428,40 @@ func (l *WebhookListener) hasBecomeState(event testkube.Event) (bool, error) {
346
428
347
429
return false , nil
348
430
}
431
+
432
+ type configKeyValue [T any ] struct {
433
+ Key string
434
+ Value T
435
+ }
436
+
437
+ type configKeyValues [T any ] []configKeyValue [T ]
438
+
439
+ // getMapHashedMetadata returns map hashed metadata
440
+ func getMapHashedMetadata [T any ](data map [string ]T ) (string , error ) {
441
+ var slice configKeyValues [T ]
442
+ for key , value := range data {
443
+ slice = append (slice , configKeyValue [T ]{Key : key , Value : value })
444
+ }
445
+
446
+ sort .Slice (slice , func (i , j int ) bool {
447
+ return slice [i ].Key < slice [j ].Key
448
+ })
449
+
450
+ return getSliceHashedMetadata (slice )
451
+ }
452
+
453
+ // getSliceHashedMetadata returns slice hashed metadata
454
+ func getSliceHashedMetadata [T any ](slice []T ) (string , error ) {
455
+ result , err := json .Marshal (slice )
456
+ if err != nil {
457
+ return "" , err
458
+ }
459
+
460
+ return getTextHashedMetadata (result ), nil
461
+ }
462
+
463
+ // getTextHashedMetadata returns text hashed metadata
464
+ func getTextHashedMetadata (result []byte ) string {
465
+
466
+ return fmt .Sprintf ("%x" , sha256 .Sum256 (result ))
467
+ }
0 commit comments