-
Notifications
You must be signed in to change notification settings - Fork 292
/
config.go
798 lines (669 loc) · 26.1 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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
package config
import (
_ "embed"
"fmt"
"regexp"
"strings"
"time"
"github.com/knadh/koanf"
koanfyaml "github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/rawbytes"
"github.com/mitchellh/mapstructure"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
//go:embed default.yaml
var defaultConfiguration []byte
const (
configEnvVariablePrefix = "BOTKUBE_"
configDelimiter = "."
camelCaseDelimiter = "__"
nestedFieldDelimiter = "_"
)
const (
// allValuesPattern represents a keyword for allowing all values.
allValuesPattern = ".*"
)
const (
// RBACDefaultGroup describes default rbac group name.
RBACDefaultGroup = "botkube-plugins-default"
// RBACDefaultUser describes default rbac user name.
RBACDefaultUser = "botkube-internal-static-user"
)
// EventType to watch
type EventType string
const (
// CreateEvent when resource is created
CreateEvent EventType = "create"
// UpdateEvent when resource is updated
UpdateEvent EventType = "update"
// DeleteEvent when resource deleted
DeleteEvent EventType = "delete"
// ErrorEvent on errors in resources
ErrorEvent EventType = "error"
// WarningEvent for warning events
WarningEvent EventType = "warning"
// NormalEvent for Normal events
NormalEvent EventType = "normal"
// InfoEvent for insignificant Info events
InfoEvent EventType = "info"
// AllEvent to watch all events
AllEvent EventType = "all"
)
// Level type to store event levels
type Level string
const (
// Info level
Info Level = "info"
// Warn level
Warn Level = "warn"
// Debug level
Debug Level = "debug"
// Error level
Error Level = "error"
// Critical level
Critical Level = "critical"
)
// CommPlatformIntegration defines integrations with communication platforms.
type CommPlatformIntegration string
const (
// SocketSlackCommPlatformIntegration defines Slack integration.
SocketSlackCommPlatformIntegration CommPlatformIntegration = "socketSlack"
// CloudSlackCommPlatformIntegration defines Slack integration.
CloudSlackCommPlatformIntegration CommPlatformIntegration = "cloudSlack"
// MattermostCommPlatformIntegration defines Mattermost integration.
MattermostCommPlatformIntegration CommPlatformIntegration = "mattermost"
// TeamsCommPlatformIntegration defines Teams integration.
TeamsCommPlatformIntegration CommPlatformIntegration = "teams"
// CloudTeamsCommPlatformIntegration defines Teams integration.
CloudTeamsCommPlatformIntegration CommPlatformIntegration = "cloudTeams"
// DiscordCommPlatformIntegration defines Discord integration.
DiscordCommPlatformIntegration CommPlatformIntegration = "discord"
// ElasticsearchCommPlatformIntegration defines Elasticsearch integration.
ElasticsearchCommPlatformIntegration CommPlatformIntegration = "elasticsearch"
// WebhookCommPlatformIntegration defines an outgoing webhook integration.
WebhookCommPlatformIntegration CommPlatformIntegration = "webhook"
// PagerDutyCommPlatformIntegration defines an outgoing PagerDuty integration.
PagerDutyCommPlatformIntegration CommPlatformIntegration = "pagerDuty"
)
func (c CommPlatformIntegration) IsInteractive() bool {
return c == SocketSlackCommPlatformIntegration || c == CloudSlackCommPlatformIntegration || c == CloudTeamsCommPlatformIntegration
}
// String returns string platform name.
func (c CommPlatformIntegration) String() string {
return string(c)
}
func (c CommPlatformIntegration) DisplayName() string {
switch c {
case SocketSlackCommPlatformIntegration, CloudSlackCommPlatformIntegration:
return "Slack"
case TeamsCommPlatformIntegration, CloudTeamsCommPlatformIntegration:
return "Teams"
case MattermostCommPlatformIntegration:
return "Mattermost"
case DiscordCommPlatformIntegration:
return "Discord"
}
return ""
}
// IntegrationType describes the type of integration with a communication platform.
type IntegrationType string
const (
// BotIntegrationType describes two-way integration.
BotIntegrationType IntegrationType = "bot"
// SinkIntegrationType describes one-way integration.
SinkIntegrationType IntegrationType = "sink"
)
// Config structure of configuration yaml file
type Config struct {
Actions Actions `yaml:"actions" validate:"dive"`
Sources map[string]Sources `yaml:"sources" validate:"dive"`
Executors map[string]Executors `yaml:"executors" validate:"dive"`
Aliases Aliases `yaml:"aliases" validate:"dive"`
Communications map[string]Communications `yaml:"communications" validate:"required,min=1,dive"`
Analytics Analytics `yaml:"analytics"`
Settings Settings `yaml:"settings"`
ConfigWatcher CfgWatcher `yaml:"configWatcher"`
Plugins PluginManagement `yaml:"plugins"`
}
// PluginManagement holds Botkube plugin management related configuration.
type PluginManagement struct {
CacheDir string `yaml:"cacheDir"`
Repositories map[string]PluginsRepository `yaml:"repositories"`
IncomingWebhook IncomingWebhook `yaml:"incomingWebhook"`
RestartPolicy PluginRestartPolicy `yaml:"restartPolicy"`
HealthCheckInterval time.Duration `yaml:"healthCheckInterval"`
}
type PluginRestartPolicy struct {
Type PluginRestartPolicyType `yaml:"type"`
Threshold int `yaml:"threshold"`
}
type PluginRestartPolicyType string
const (
KeepAgentRunningWhenThresholdReached PluginRestartPolicyType = "DeactivatePlugin"
RestartAgentWhenThresholdReached PluginRestartPolicyType = "RestartAgent"
)
func (p PluginRestartPolicyType) ToLower() string {
return strings.ToLower(string(p))
}
// PluginsRepository holds the Plugin repository information.
type PluginsRepository struct {
URL string `yaml:"url"`
Headers map[string]string
}
// IncomingWebhook contains configuration for incoming source webhook.
type IncomingWebhook struct {
Enabled bool `yaml:"enabled"`
Port int `yaml:"port"`
// InClusterBaseURL is the in-cluster URL of the incoming webhook. Passed for plugins in context.
InClusterBaseURL string `yaml:"inClusterBaseURL"`
}
// CloudSlackChannel contains configuration bindings per channel.
type CloudSlackChannel struct {
ChannelBindingsByName `yaml:",inline" mapstructure:",squash"`
// ChannelID is the Slack ID of the channel.
// Currently, it is used for AI plugin as it has ability to fetch the Botkube Agent configuration.
// Later it can be used for deep linking to a given channel, see: https://api.slack.com/reference/deep-linking#app_channel
ChannelID string `yaml:"channelID"`
// Alias is an optional public alias for a private channel.
Alias *string `yaml:"alias,omitempty"`
}
// ChannelBindingsByName contains configuration bindings per channel.
type ChannelBindingsByName struct {
Name string `yaml:"name"`
Notification ChannelNotification `yaml:"notification"` // TODO: rename to `notifications` later
Bindings BotBindings `yaml:"bindings"`
MessageTriggers []TextMessageTriggers `yaml:"messageTriggers"`
}
// Identifier returns ChannelBindingsByName identifier.
func (c ChannelBindingsByName) Identifier() string {
return c.Name
}
// GetBotBindings returns associated bindings.
func (c ChannelBindingsByName) GetBotBindings() BotBindings {
return c.Bindings
}
type TextMessageTriggerEvent string
const (
MessageTriggerChannelEvent TextMessageTriggerEvent = "ChannelMessage"
)
// TextMessageTriggers contains information about matching messages and their associated commands.
type TextMessageTriggers struct {
Event TextMessageTriggerEvent `yaml:"event"`
Text RegexConstraints `yaml:"text"`
Users UsersMessageConstraints `yaml:"users"`
Command string `yaml:"command"`
Executors []string `yaml:"executors"`
ProcessedEmojiIndicator *string `yaml:"processedEmojiIndicator,omitempty"`
}
type UsersMessageConstraints struct {
Exclude []string `yaml:"exclude"`
}
func (m *TextMessageTriggers) IsUserExcluded(userID string) bool {
for _, user := range m.Users.Exclude {
toIgnore, _, _ := strings.Cut(user, ":")
if toIgnore == userID {
return true
}
}
return false
}
// ChannelBindingsByID contains configuration bindings per channel.
type ChannelBindingsByID struct {
ID string `yaml:"id"`
Notification ChannelNotification `yaml:"notification"` // TODO: rename to `notifications` later
Bindings BotBindings `yaml:"bindings"`
}
// Identifier returns ChannelBindingsByID identifier.
func (c ChannelBindingsByID) Identifier() string {
return c.ID
}
// GetBotBindings returns associated bindings.
func (c ChannelBindingsByID) GetBotBindings() BotBindings {
return c.Bindings
}
// BotBindings contains configuration for possible Bot bindings.
type BotBindings struct {
Sources []string `yaml:"sources"`
Executors []string `yaml:"executors"`
}
// SinkBindings contains configuration for possible Sink bindings.
type SinkBindings struct {
Sources []string `yaml:"sources"`
}
// Actions contains configuration for Botkube app event automations.
type Actions map[string]Action
// Action contains configuration for Botkube app event automations.
type Action struct {
Enabled bool `yaml:"enabled"`
DisplayName string `yaml:"displayName"`
Command string `yaml:"command" validate:"required_if=Enabled true"`
Bindings ActionBindings `yaml:"bindings"`
}
// ActionBindings contains configuration for action bindings.
type ActionBindings struct {
Sources []string `yaml:"sources"`
Executors []string `yaml:"executors"`
}
// Sources contains configuration for Botkube app sources.
type Sources struct {
DisplayName string `yaml:"displayName"`
Plugins Plugins `yaml:",inline" koanf:",remain"`
}
// GetPlugins returns Sources.Plugins.
func (s Sources) GetPlugins() Plugins {
return s.Plugins
}
// Plugins contains plugins configuration parameters defined in groups.
type Plugins map[string]Plugin
// Plugin contains plugin specific configuration.
type Plugin struct {
Enabled bool
Config any
Context PluginContext
}
// PluginContext defines the context for given plugin.
type PluginContext struct {
// RBAC defines the RBAC rules for given plugin.
RBAC *PolicyRule `yaml:"rbac,omitempty"`
}
// PolicyRule is the RBAC rule.
type PolicyRule struct {
// User is the policy subject for user.
User UserPolicySubject `yaml:"user"`
// Group is the policy subject for group.
Group GroupPolicySubject `yaml:"group"`
}
// GroupPolicySubject is the RBAC subject.
type GroupPolicySubject struct {
// Type is the type of policy subject.
Type PolicySubjectType `yaml:"type"`
// Static is static reference of subject for given static policy rule.
Static GroupStaticSubject `yaml:"static"`
// Prefix is optional string prefixed to subjects.
Prefix string `yaml:"prefix"`
}
// GroupStaticSubject references static subjects for given static policy rule.
type GroupStaticSubject struct {
// Values is the name of the subject.
Values []string `yaml:"values"`
}
// UserPolicySubject is the RBAC subject.
type UserPolicySubject struct {
// Type is the type of policy subject.
Type PolicySubjectType `yaml:"type"`
// Static is static reference of subject for given static policy rule.
Static UserStaticSubject `yaml:"static"`
// Prefix is optional string prefixed to subjects.
Prefix string `yaml:"prefix"`
}
// UserStaticSubject references static subjects for given static policy rule.
type UserStaticSubject struct {
// Value is the name of the subject.
Value string `yaml:"value"`
}
// PolicySubjectType defines the types for policy subjects.
type PolicySubjectType string
const (
// EmptyPolicySubjectType is the empty policy type.
EmptyPolicySubjectType PolicySubjectType = ""
// StaticPolicySubjectType is the static policy type.
StaticPolicySubjectType PolicySubjectType = "Static"
// ChannelNamePolicySubjectType is the channel name policy type.
ChannelNamePolicySubjectType PolicySubjectType = "ChannelName"
)
// Executors contains executors configuration parameters.
type Executors struct {
DisplayName string `yaml:"displayName"`
Plugins Plugins `yaml:",inline" koanf:",remain"`
}
// CollectCommandPrefixes returns list of command prefixes for all executors, even disabled ones.
func (e Executors) CollectCommandPrefixes() []string {
var prefixes []string
for pluginName := range e.Plugins {
prefixes = append(prefixes, ExecutorNameForKey(pluginName))
}
return prefixes
}
// GetPlugins returns Executors.Plugins
func (e Executors) GetPlugins() Plugins {
return e.Plugins
}
// Aliases contains aliases configuration.
type Aliases map[string]Alias
// Alias defines alias configuration for a given command.
type Alias struct {
Command string `yaml:"command" validate:"required"`
DisplayName string `yaml:"displayName"`
}
// Analytics contains configuration parameters for analytics collection.
type Analytics struct {
Disable bool `yaml:"disable"`
}
// RegexConstraints contains a list of allowed and excluded values.
type RegexConstraints struct {
// Include contains a list of allowed values.
// It can also contain a regex expressions:
// - ".*" - to specify all values.
Include []string `yaml:"include"`
// Exclude contains a list of values to be ignored even if allowed by Include.
// It can also contain a regex expressions:
// - "test-.*" - to specify all values with `test-` prefix.
Exclude []string `yaml:"exclude,omitempty"`
}
// AreConstraintsDefined checks whether the RegexConstraints has any Include/Exclude configuration.
func (r *RegexConstraints) AreConstraintsDefined() bool {
return len(r.Include) > 0 || len(r.Exclude) > 0
}
// IsAllowed checks if a given value is allowed based on the config.
// Firstly, it checks if the value is excluded. If not, then it checks if the value is included.
func (r *RegexConstraints) IsAllowed(value string) (bool, error) {
if r == nil {
return false, nil
}
// 1. Check if excluded
if len(r.Exclude) > 0 {
for _, excludeValue := range r.Exclude {
if strings.TrimSpace(excludeValue) == "" {
continue
}
// exact match
if excludeValue == value {
return false, nil
}
// regexp
matched, err := regexp.MatchString(excludeValue, value)
if err != nil {
return false, fmt.Errorf("while matching %q with exclude regex %q: %v", value, excludeValue, err)
}
if matched {
return false, nil
}
}
}
// 2. Check if included, if matched, return true
if len(r.Include) > 0 {
for _, includeValue := range r.Include {
// exact match
if includeValue == value {
return true, nil
}
// regexp
matched, err := regexp.MatchString(includeValue, value)
if err != nil {
return false, fmt.Errorf("while matching %q with include regex %q: %v", value, includeValue, err)
}
if matched {
return true, nil
}
}
}
// 2.1. If not included, return false
return false, nil
}
// ChannelNotification contains notification configuration for a given platform.
type ChannelNotification struct {
Disabled bool `yaml:"disabled"`
}
// Communications contains communication platforms that are supported.
type Communications struct {
SocketSlack SocketSlack `yaml:"socketSlack,omitempty"`
CloudSlack CloudSlack `yaml:"cloudSlack,omitempty"`
Mattermost Mattermost `yaml:"mattermost,omitempty"`
Discord Discord `yaml:"discord,omitempty"`
CloudTeams CloudTeams `yaml:"cloudTeams,omitempty"`
Webhook Webhook `yaml:"webhook,omitempty"`
Elasticsearch Elasticsearch `yaml:"elasticsearch,omitempty"`
PagerDuty PagerDuty `yaml:"pagerDuty,omitempty"`
}
// SocketSlack configuration to authentication and send notifications
type SocketSlack struct {
Enabled bool `yaml:"enabled"`
Channels IdentifiableMap[ChannelBindingsByName] `yaml:"channels" validate:"required_if=Enabled true,dive,omitempty,min=1"`
BotToken string `yaml:"botToken,omitempty"`
AppToken string `yaml:"appToken,omitempty"`
}
// CloudSlack configuration for multi-slack support
type CloudSlack struct {
Enabled bool `yaml:"enabled"`
Channels IdentifiableMap[CloudSlackChannel] `yaml:"channels" validate:"required_if=Enabled true,dive,omitempty,min=1"`
Token string `yaml:"token"`
BotID string `yaml:"botID,omitempty"`
Server GRPCServer `yaml:"server"`
ExecutionEventStreamingDisabled bool `yaml:"executionEventStreamingDisabled"`
}
// GRPCServer config for gRPC server
type GRPCServer struct {
URL string `yaml:"url"`
DisableTransportSecurity bool `yaml:"disableTransportSecurity"`
TLS GRPCServerTLSConfig `yaml:"tls"`
}
// GRPCServerTLSConfig describes gRPC server TLS configuration.m
type GRPCServerTLSConfig struct {
CACertificate []byte `yaml:"caCertificate,omitempty"`
UseSystemCertPool bool `yaml:"useSystemCertPool"`
InsecureSkipVerify bool `yaml:"insecureSkipVerify"`
}
// Elasticsearch config auth settings
type Elasticsearch struct {
Enabled bool `yaml:"enabled"`
Username string `yaml:"username"`
Password string `yaml:"password"`
Server string `yaml:"server"`
SkipTLSVerify bool `yaml:"skipTLSVerify"`
AWSSigning AWSSigning `yaml:"awsSigning"`
Indices map[string]ELSIndex `yaml:"indices" validate:"required_if=Enabled true,dive,omitempty,min=1"`
LogLevel string `yaml:"logLevel"`
}
// AWSSigning contains AWS configurations
type AWSSigning struct {
Enabled bool `yaml:"enabled"`
AWSRegion string `yaml:"awsRegion"`
RoleArn string `yaml:"roleArn"`
}
// ELSIndex settings for ELS
type ELSIndex struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Shards int `yaml:"shards"`
Replicas int `yaml:"replicas"`
Bindings SinkBindings `yaml:"bindings"`
}
// Mattermost configuration to authentication and send notifications
type Mattermost struct {
Enabled bool `yaml:"enabled"`
BotName string `yaml:"botName"`
URL string `yaml:"url"`
Token string `yaml:"token"`
Team string `yaml:"team"`
Channels IdentifiableMap[ChannelBindingsByName] `yaml:"channels" validate:"required_if=Enabled true,dive,omitempty,min=1"`
}
// Teams creds for authentication with MS Teams
type Teams struct {
Enabled bool `yaml:"enabled"`
BotName string `yaml:"botName,omitempty"`
AppID string `yaml:"appID,omitempty"`
AppPassword string `yaml:"appPassword,omitempty"`
Port string `yaml:"port"`
MessagePath string `yaml:"messagePath,omitempty"`
Bindings BotBindings `yaml:"bindings" validate:"required_if=Enabled true"`
}
// CloudTeams configuration for cloud MS Teams.
type CloudTeams struct {
Enabled bool `yaml:"enabled"`
BotName string `yaml:"botName"`
Server GRPCServer `yaml:"server"`
Teams []TeamsBindings `yaml:"teams" validate:"required_if=Enabled true,dive,omitempty,min=1"`
}
type TeamsBindings struct {
ID string `yaml:"id"`
Channels IdentifiableMap[ChannelBindingsByID] `yaml:"channels" validate:"dive,omitempty,min=1"`
}
// Discord configuration for authentication and send notifications
type Discord struct {
Enabled bool `yaml:"enabled"`
Token string `yaml:"token"`
BotID string `yaml:"botID"`
Channels IdentifiableMap[ChannelBindingsByID] `yaml:"channels" validate:"required_if=Enabled true,dive,omitempty,min=1"`
}
// Webhook configuration to send notifications
type Webhook struct {
Enabled bool `yaml:"enabled"`
URL string `yaml:"url"`
Bindings SinkBindings `yaml:"bindings" validate:"required_if=Enabled true"`
}
// PagerDuty describes the PagerDuty sink.
type PagerDuty struct {
// Enabled indicates if the PagerDuty sink is enabled.
Enabled bool `yaml:"enabled"`
// Bindings are the bindings for the PagerDuty sink.
Bindings SinkBindings `yaml:"bindings" validate:"required_if=Enabled true"`
// IntegrationKey is the PagerDuty integration key generated for Events v2 API.
IntegrationKey string `yaml:"integrationKey" validate:"required_if=Enabled true"`
// V2EventsAPIBasePath is the Events v2 API URL base path. Defaults to https://events.pagerduty.com.
V2EventsAPIBasePath string
}
// CfgWatcher describes configuration for watching the configuration.
type CfgWatcher struct {
Enabled bool `yaml:"enabled"`
Remote RemoteCfgWatcher `yaml:"remote"`
InCluster InClusterCfgWatcher `yaml:"inCluster"`
Deployment K8sResourceRef `yaml:"deployment"`
}
// RemoteCfgWatcher describes configuration for watching the configuration using remote config provider.
type RemoteCfgWatcher struct {
PollInterval time.Duration `yaml:"pollInterval"`
}
// InClusterCfgWatcher describes configuration for watching the configuration using in-cluster config provider.
type InClusterCfgWatcher struct {
InformerResyncPeriod time.Duration `yaml:"informerResyncPeriod"`
}
// Settings contains Botkube's related configuration.
type Settings struct {
ClusterName string `yaml:"clusterName"`
UpgradeNotifier bool `yaml:"upgradeNotifier"`
SystemConfigMap K8sResourceRef `yaml:"systemConfigMap"`
PersistentConfig PersistentConfig `yaml:"persistentConfig"`
MetricsPort string `yaml:"metricsPort"`
HealthPort string `yaml:"healthPort"`
Log Logger `yaml:"log"`
InformersResyncPeriod time.Duration `yaml:"informersResyncPeriod"`
Kubeconfig string `yaml:"kubeconfig"`
SACredentialsPathPrefix string `yaml:"saCredentialsPathPrefix"`
}
// Formatter log formatter
type Formatter string
const (
// FormatterText text formatter for logging
FormatterText Formatter = "text"
// FormatterJSON json formatter for logging
FormatterJSON Formatter = "json"
)
// Logger holds logger configuration parameters.
type Logger struct {
Level string `yaml:"level"`
DisableColors bool `yaml:"disableColors"`
Formatter Formatter `yaml:"formatter"`
}
// PersistentConfig contains configuration for persistent storage.
type PersistentConfig struct {
Startup PartialPersistentConfig `yaml:"startup"`
Runtime PartialPersistentConfig `yaml:"runtime"`
}
// PartialPersistentConfig contains configuration for persistent storage of a given type.
type PartialPersistentConfig struct {
FileName string `yaml:"fileName"`
ConfigMap K8sResourceRef `yaml:"configMap"`
}
// K8sResourceRef holds the configuration for a Kubernetes resource.
type K8sResourceRef struct {
Name string `yaml:"name,omitempty"`
Namespace string `yaml:"namespace,omitempty"`
}
func (eventType EventType) String() string {
return string(eventType)
}
// LoadWithDefaultsDetails holds the LoadWithDefaults function details.
type LoadWithDefaultsDetails struct {
ValidateWarnings error
}
// LoadWithDefaults loads new configuration from files and environment variables.
func LoadWithDefaults(configs [][]byte) (*Config, LoadWithDefaultsDetails, error) {
k := koanf.New(configDelimiter)
// load default settings
if err := k.Load(rawbytes.Provider(defaultConfiguration), koanfyaml.Parser()); err != nil {
return nil, LoadWithDefaultsDetails{}, fmt.Errorf("while loading default configuration: %w", err)
}
// merge with user configs
for _, rawCfg := range configs {
if err := k.Load(rawbytes.Provider(rawCfg), koanfyaml.Parser()); err != nil {
return nil, LoadWithDefaultsDetails{}, err
}
}
// LoadWithDefaults environment variables and merge into the loaded config.
err := k.Load(env.Provider(
configEnvVariablePrefix,
configDelimiter,
normalizeConfigEnvName,
), nil)
if err != nil {
return nil, LoadWithDefaultsDetails{}, err
}
var cfg Config
err = k.UnmarshalWithConf("", &cfg, koanf.UnmarshalConf{
DecoderConfig: &mapstructure.DecoderConfig{
Squash: true, // needed to properly unmarshal CloudSlack channel's ChannelBindingsByName
// also use defaults from koanf.UnmarshalWithConf
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
mapstructure.TextUnmarshallerHookFunc()),
Metadata: nil,
Result: &cfg,
WeaklyTypedInput: true,
},
})
if err != nil {
return nil, LoadWithDefaultsDetails{}, err
}
result, err := ValidateStruct(cfg)
if err != nil {
return nil, LoadWithDefaultsDetails{}, fmt.Errorf("while validating loaded configuration: %w", err)
}
if err := result.Criticals.ErrorOrNil(); err != nil {
return nil, LoadWithDefaultsDetails{}, fmt.Errorf("found critical validation errors: %w", err)
}
return &cfg, LoadWithDefaultsDetails{
ValidateWarnings: result.Warnings.ErrorOrNil(),
}, nil
}
func normalizeConfigEnvName(name string) string {
name = strings.TrimPrefix(name, configEnvVariablePrefix)
words := strings.Split(name, camelCaseDelimiter)
toTitle := cases.Title(language.AmericanEnglish)
var buff strings.Builder
buff.WriteString(strings.ToLower(words[0]))
for _, word := range words[1:] {
word = strings.ToLower(word)
buff.WriteString(toTitle.String(word))
}
return strings.ReplaceAll(buff.String(), nestedFieldDelimiter, configDelimiter)
}
// IdentifiableMap provides an option to construct an indexable map for identifiable items.
type IdentifiableMap[T Identifiable] map[string]T
// Identifiable exports an Identifier method.
type Identifiable interface {
Identifier() string
}
// GetByIdentifier gets an item from a map by identifier.
func (t IdentifiableMap[T]) GetByIdentifier(val string) (T, bool) {
for _, v := range t {
if v.Identifier() != val {
continue
}
return v, true
}
var empty T
return empty, false
}