Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self service #189

Draft
wants to merge 20 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Config struct {
DefaultTriggers []string
// ServiceDefaultTriggers holds list of default triggers per service
ServiceDefaultTriggers map[string][]string
Namespace string
}

// Returns list of destinations for the specified trigger
Expand Down Expand Up @@ -76,6 +77,7 @@ func ParseConfig(configMap *v1.ConfigMap, secret *v1.Secret) (*Config, error) {
Triggers: map[string][]triggers.Condition{},
ServiceDefaultTriggers: map[string][]string{},
Templates: map[string]services.Notification{},
Namespace: configMap.Namespace,
}
if subscriptionYaml, ok := configMap.Data["subscriptions"]; ok {
if err := yaml.Unmarshal([]byte(subscriptionYaml), &cfg.Subscriptions); err != nil {
Expand Down
213 changes: 198 additions & 15 deletions pkg/api/factory.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
log "github.com/sirupsen/logrus"
"sync"

v1 "k8s.io/api/core/v1"
Expand All @@ -18,27 +19,56 @@ type Settings struct {
SecretName string
// InitGetVars returns a function that produces notifications context variables
InitGetVars func(cfg *Config, configMap *v1.ConfigMap, secret *v1.Secret) (GetVars, error)
// Default namespace for ConfigMap and Secret.
// For self-service notification, we get notification configurations from rollout resource namespace
// and also the default namespace
Namespace string
}

// Factory creates an API instance
type Factory interface {
GetAPI() (API, error)
}

// For self-service notification, factory creates a map of APIs that include
// api in the namespace specified in input parameter
// and api in the namespace specified in the Settings
type FactoryWithMultipleAPIs interface {
GetAPIsWithNamespaceV2(namespace string) (map[string]API, error)
}

type apiFactory struct {
Settings

cmLister v1listers.ConfigMapNamespaceLister
secretLister v1listers.SecretNamespaceLister
lock sync.Mutex
api API

// For self-service notification
cmInformer cache.SharedIndexInformer
secretsInformer cache.SharedIndexInformer
apiMap map[string]API
cacheList []apisCache
}

type apisCache struct {
api API
namespace string
refresh bool
}

func NewFactory(settings Settings, namespace string, secretsInformer cache.SharedIndexInformer, cmInformer cache.SharedIndexInformer) *apiFactory {
factory := &apiFactory{
Settings: settings,
cmLister: v1listers.NewConfigMapLister(cmInformer.GetIndexer()).ConfigMaps(namespace),
secretLister: v1listers.NewSecretLister(secretsInformer.GetIndexer()).Secrets(namespace),

// For self-service notification
cmInformer: cmInformer,
secretsInformer: secretsInformer,
apiMap: make(map[string]API),
cacheList: []apisCache{},
}

secretsInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
Expand Down Expand Up @@ -70,12 +100,12 @@ func (f *apiFactory) invalidateIfHasName(name string, obj interface{}) {
return
}
if metaObj.GetName() == name {
f.invalidateCache()
f.invalidateCache(metaObj.GetNamespace())
}
}

func (f *apiFactory) getConfigMapAndSecret() (*v1.ConfigMap, *v1.Secret, error) {
cm, err := f.cmLister.Get(f.ConfigMapName)
func (f *apiFactory) getConfigMapAndSecretWithListers(cmLister v1listers.ConfigMapNamespaceLister, secretLister v1listers.SecretNamespaceLister) (*v1.ConfigMap, *v1.Secret, error) {
cm, err := cmLister.Get(f.ConfigMapName)
if err != nil {
if errors.IsNotFound(err) {
cm = &v1.ConfigMap{}
Expand All @@ -84,7 +114,7 @@ func (f *apiFactory) getConfigMapAndSecret() (*v1.ConfigMap, *v1.Secret, error)
}
}

secret, err := f.secretLister.Get(f.SecretName)
secret, err := secretLister.Get(f.SecretName)
if err != nil {
if errors.IsNotFound(err) {
secret = &v1.Secret{}
Expand All @@ -96,33 +126,186 @@ func (f *apiFactory) getConfigMapAndSecret() (*v1.ConfigMap, *v1.Secret, error)
return cm, secret, err
}

func (f *apiFactory) invalidateCache() {
func (f *apiFactory) getConfigMapAndSecret(namespace string) (*v1.ConfigMap, *v1.Secret, error) {
cmLister := v1listers.NewConfigMapLister(f.cmInformer.GetIndexer()).ConfigMaps(namespace)
secretLister := v1listers.NewSecretLister(f.secretsInformer.GetIndexer()).Secrets(namespace)

return f.getConfigMapAndSecretWithListers(cmLister, secretLister)
}

func (f *apiFactory) invalidateCache(namespace string) {
f.lock.Lock()
defer f.lock.Unlock()
f.api = nil

f.apiMap[namespace] = nil

for _, mycache := range f.cacheList {
if mycache.namespace == namespace {
mycache.refresh = true
mycache.api = nil
}
}
}

func (f *apiFactory) GetAPI() (API, error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.api == nil {
cm, secret, err := f.getConfigMapAndSecret()
if err != nil {
return nil, err
}
cfg, err := ParseConfig(cm, secret)
if err != nil {
return nil, err
}
getVars, err := f.InitGetVars(cfg, cm, secret)
cm, secret, err := f.getConfigMapAndSecretWithListers(f.cmLister, f.secretLister)
if err != nil {
return nil, err
}
api, err := NewAPI(*cfg, getVars)

api, err := f.getApiFromConfigmapAndSecret(cm, secret)
if err != nil {
return nil, err
}
f.api = api
}
return f.api, nil
}

// For self-service notification, we need a map of apis which include api in the namespace and api in the setting's namespace
func (f *apiFactory) GetAPIsWithNamespace(namespace string) (map[string]API, error) {
f.lock.Lock()
defer f.lock.Unlock()

apis := make(map[string]API)

if f.apiMap[namespace] != nil && f.apiMap[f.Settings.Namespace] != nil {
apis[namespace] = f.apiMap[namespace]
apis[f.Settings.Namespace] = f.apiMap[f.Settings.Namespace]
return apis, nil
}

if f.apiMap[namespace] != nil {
apis[namespace] = f.apiMap[namespace]
api, err := f.getApiFromNamespace(f.Settings.Namespace)
if err == nil {
apis[f.Settings.Namespace] = api
f.apiMap[f.Settings.Namespace] = api
} else {
log.Warnf("getApiFromNamespace %s got error %s", f.Settings.Namespace, err)
}
return apis, nil
}

if f.apiMap[f.Settings.Namespace] != nil {
apis[f.Settings.Namespace] = f.apiMap[f.Settings.Namespace]
api, err := f.getApiFromNamespace(namespace)
if err == nil {
apis[namespace] = api
f.apiMap[namespace] = api
} else {
log.Warnf("getApiFromNamespace %s got error %s", namespace, err)
}
return apis, nil
}

apiFromNamespace, errApiFromNamespace := f.getApiFromNamespace(namespace)
apiFromSettings, errApiFromSettings := f.getApiFromNamespace(f.Settings.Namespace)

if errApiFromNamespace == nil {
apis[namespace] = apiFromNamespace
f.apiMap[namespace] = apiFromNamespace
} else {
log.Warnf("getApiFromNamespace %s got error %s", namespace, errApiFromNamespace)
}

if errApiFromSettings == nil {
apis[f.Settings.Namespace] = apiFromSettings
f.apiMap[f.Settings.Namespace] = apiFromSettings
} else {
log.Warnf("getApiFromNamespace %s got error %s", f.Settings.Namespace, errApiFromSettings)
}

// Only return error when we received error from both namespace provided in the input paremeter and settings' namespace
if errApiFromNamespace != nil && errApiFromSettings != nil {
return apis, errApiFromSettings
} else {
return apis, nil
}
}

// For self-service notification, we need a map of apis which include api in the namespace and api in the setting's namespace
func (f *apiFactory) GetAPIsWithNamespaceV2(namespace string) (map[string]API, error) {
f.lock.Lock()
defer f.lock.Unlock()

apis := make(map[string]API)

// namespaces to look for notification configurations
namespaces := []string{namespace}
if f.Settings.Namespace != "" && f.Settings.Namespace != namespace {
namespaces = append(namespaces, f.Settings.Namespace)
}

for _, namespace := range namespaces {
//Look up the cacheList
//Exist in cacheList and does not need refresh, then use it
//Exist in cacheList and needs refresh, then retrieve it
//Doesn't exist in cacheList, get it and put in cacheList
foundInCache := false
for _, cache := range f.cacheList {
if cache.namespace == namespace {
foundInCache = true
if !cache.refresh {
//Found in cache, and no need to refresh
if cache.api != nil {
apis[namespace] = cache.api
}
} else {
//Found in cache, and need refresh
api, err := f.getApiFromNamespace(namespace)
if err == nil {
apis[namespace] = api
cache.api = api
cache.refresh = false
} else {
log.Warnf("getApiFromNamespace %s got error %s", namespace, err)
}
}
break
}
}

if !foundInCache {
api, err := f.getApiFromNamespace(namespace)
if err == nil {
apis[namespace] = api
myCache := apisCache{refresh: false, api: api, namespace: namespace}
f.cacheList = append(f.cacheList, myCache)
} else {
log.Warnf("getApiFromNamespace %s got error %s", namespace, err)
}
}
}

return apis, nil
}

func (f *apiFactory) getApiFromNamespace(namespace string) (API, error) {
cm, secret, err := f.getConfigMapAndSecret(namespace)
if err != nil {
return nil, err
}
return f.getApiFromConfigmapAndSecret(cm, secret)

}

func (f *apiFactory) getApiFromConfigmapAndSecret(cm *v1.ConfigMap, secret *v1.Secret) (API, error) {
cfg, err := ParseConfig(cm, secret)
if err != nil {
return nil, err
}
getVars, err := f.InitGetVars(cfg, cm, secret)
if err != nil {
return nil, err
}
api, err := NewAPI(*cfg, getVars)
if err != nil {
return nil, err
}
return api, nil
}
Loading