-
Notifications
You must be signed in to change notification settings - Fork 0
/
provider.go
144 lines (129 loc) · 5.81 KB
/
provider.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
package config
import (
"context"
"fmt"
"github.com/kube-cicd/pipelines-feedback-core/internal/config"
"github.com/kube-cicd/pipelines-feedback-core/pkgs/contract"
"github.com/kube-cicd/pipelines-feedback-core/pkgs/logging"
"github.com/kube-cicd/pipelines-feedback-core/pkgs/store"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
"strings"
)
type ConfigurationProviderInterface interface {
FetchContextual(component string, namespace string, pipeline contract.PipelineInfo) Data
FetchGlobal(component string) Data
FetchSecretKey(ctx context.Context, name string, namespace string, key string, cache bool) (string, error)
FetchFromFieldOrSecret(ctx context.Context, data *Data, namespace string, fieldKey string, referenceKey string, referenceSecretNameKey string) (string, error)
}
// NewConfigurationProvider is a constructor
func NewConfigurationProvider(docStore config.IndexedDocumentStore, logger *logging.InternalLogger,
client v1.CoreV1Interface, kvStore store.Operator, cfgSchema Validator) (ConfigurationProviderInterface, error) {
return &ConfigurationProvider{
docStore: docStore,
logger: logger,
secretsClient: client,
stateStore: kvStore,
cfgSchema: cfgSchema,
}, nil
}
// ConfigurationProvider is serving already collected configuration. Served configuration is already merged from various sources
type ConfigurationProvider struct {
docStore config.IndexedDocumentStore
logger *logging.InternalLogger
secretsClient v1.CoreV1Interface
stateStore store.Operator
cfgSchema Validator
}
// todo: implement CollectOnRequest()
// FetchContextual is retrieving a final configuration in context of a given contract.PipelineInfo
func (cp *ConfigurationProvider) FetchContextual(component string, namespace string, pipeline contract.PipelineInfo) Data {
cp.logger.Debugf("fetchContextual(%s, %s)", namespace, pipeline.GetFullName())
endMap := make(map[string]string)
for _, doc := range cp.docStore.GetForNamespace(namespace) {
cp.logger.Debugf("fetchContextual => config '%s' available for this namespace, checking if matches", doc.Name)
if doc.IsForPipeline(pipeline) {
cp.logger.Debugf("fetchContextual(%s, %s) => using config '%s'", namespace, pipeline.GetFullName(), doc.Name)
endMap = mergeMaps(endMap, doc.Data)
}
}
return NewData(component, transformMapByComponent(endMap, component), cp.cfgSchema, cp.logger)
}
// FetchGlobal is fetching a global configuration for given component (without a context of a Pipeline)
func (cp *ConfigurationProvider) FetchGlobal(component string) Data {
endMap := make(map[string]string)
for _, doc := range cp.docStore.GetForNamespace("") {
// global configuration should not have a label selector, as label selector means the configuration
// is specific to some pipeline
if doc.HasLabelSelector() {
continue
}
endMap = mergeMaps(endMap, doc.Data)
}
return NewData(component, transformMapByComponent(endMap, component), cp.cfgSchema, cp.logger)
}
// transformMapByComponent is stripping map out of other component keys and removing the component prefixes
func transformMapByComponent(input map[string]string, component string) map[string]string {
output := make(map[string]string)
for key, val := range input {
if strings.HasPrefix(key, component+".") {
newKey := key[len(component+"."):]
output[newKey] = val
}
}
return output
}
// FetchSecretKey is fetching a key from .data section from a Kubernetes Secret, directly from the Cluster's API
// Use this method in pair with FetchContextual() to at first fetch the key name, then to fetch the secret key
func (cp *ConfigurationProvider) FetchSecretKey(ctx context.Context, name string,
namespace string, key string, cache bool) (string, error) {
if cache {
if cachedSecret := cp.stateStore.GetConfigSecretKey(namespace, name, key); cachedSecret != "" {
return cachedSecret, nil
}
}
secret, err := cp.secretsClient.Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", errors.Wrapf(err, "cannot fetch Kubernetes secret '%s/%s'", namespace, name)
}
val, exists := secret.Data[key]
if !exists {
return "", errors.New(fmt.Sprintf("the secret '%s/%s' does not contain key '%s'", namespace, name, key))
}
cp.stateStore.PushConfigSecretKey(namespace, name, key, string(val)) // Update cache
return string(val), nil
}
// FetchFromFieldOrSecret allows to use an inline secret from configuration file (if present), fallbacks to fetching a secret key from a Kubernetes secret
func (cp *ConfigurationProvider) FetchFromFieldOrSecret(ctx context.Context, data *Data, namespace string, fieldKey string, referenceKey string, referenceSecretNameKey string) (string, error) {
if data.HasKey(fieldKey) {
return data.Get(fieldKey), nil
}
if referenceSecretNameKey != "" {
// When the field with `kind: Secret` reference name is empty in the config data.
// So we do not know which Kubernetes `kind: Secret` to open
referenceSecretName := data.GetOrDefault(referenceSecretNameKey, "")
if referenceSecretName == "" {
return "", errors.New(fmt.Sprintf("'%s' should contain a valid Kubernetes secret name", referenceSecretNameKey))
}
// Try to fetch a .data.${referenceKey} from `kind: Secret` named ${referenceSecretNameKey}
referenceKeyVal := data.GetOrDefault(referenceKey, "")
val, err := cp.FetchSecretKey(ctx, referenceSecretName, namespace, referenceKeyVal, true)
if err != nil {
return "", errors.Wrap(err, "cannot fetch secret from Kubernetes")
}
return val, nil
}
// no ${referenceSecretNameKey} field specified neither ${fieldKey}
return "", nil
}
func mergeMaps(m1 map[string]string, m2 map[string]string) map[string]string {
merged := make(map[string]string)
for k, v := range m1 {
merged[k] = v
}
for key, value := range m2 {
merged[key] = value
}
return merged
}