package utils import ( "os" "sort" "strings" "sync" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/fission/fission/pkg/utils/loggerfactory" ) const ( ENV_FUNCTION_NAMESPACE string = "FISSION_FUNCTION_NAMESPACE" ENV_BUILDER_NAMESPACE string = "FISSION_BUILDER_NAMESPACE" ENV_DEFAULT_NAMESPACE string = "FISSION_DEFAULT_NAMESPACE" ENV_ADDITIONAL_NAMESPACE string = "FISSION_RESOURCE_NAMESPACES" ) type ( NamespaceResolver struct { mu sync.RWMutex // protects FissionResourceNS FunctionNamespace string BuilderNamespace string DefaultNamespace string FissionResourceNS map[string]string Logger *zap.Logger } options struct { functionNS bool builderNS bool defaultNs bool } option func(options *options) *options ) var nsResolver *NamespaceResolver func init() { nsResolver = &NamespaceResolver{ FunctionNamespace: os.Getenv(ENV_FUNCTION_NAMESPACE), BuilderNamespace: os.Getenv(ENV_BUILDER_NAMESPACE), DefaultNamespace: os.Getenv(ENV_DEFAULT_NAMESPACE), FissionResourceNS: GetNamespaces(), Logger: loggerfactory.GetLogger(), } nsResolver.Logger.Debug("namespaces", zap.String("function_namespace", nsResolver.FunctionNamespace), zap.String("builder_namespace", nsResolver.BuilderNamespace), zap.String("default_namespace", nsResolver.DefaultNamespace), zap.Any("fission_resource_namespace", listNamespaces(nsResolver.FissionResourceNS))) } // listNamespaces => convert namespaces from map to slice func listNamespaces(namespaces map[string]string) []string { ns := make([]string, 0) for _, namespace := range namespaces { ns = append(ns, namespace) } return ns } func WithBuilderNs() option { return func(options *options) *options { options.builderNS = true return options } } func WithFunctionNs() option { return func(options *options) *options { options.functionNS = true return options } } func WithDefaultNs() option { return func(options *options) *options { options.defaultNs = true return options } } // AddNamespace dynamically adds a namespace to FissionResourceNS without restarting the process. // Returns true if the namespace was newly added, false if it was already present. // Thread-safe — multiple goroutines may call this concurrently. // The label fission.io/managed=true on the Namespace object is the trigger for this call. func (nsr *NamespaceResolver) AddNamespace(ns string) bool { nsr.mu.Lock() defer nsr.mu.Unlock() if _, exists := nsr.FissionResourceNS[ns]; exists { return false } nsr.FissionResourceNS[ns] = ns if nsr.Logger != nil { nsr.Logger.Info("dynamically added namespace to resolver", zap.String("namespace", ns)) } return true } // Snapshot returns a stable copy of the currently registered resource namespaces. // The returned slice is detached from the internal mutable map and safe to iterate. func (nsr *NamespaceResolver) Snapshot() []string { nsr.mu.RLock() defer nsr.mu.RUnlock() namespaces := listNamespaces(nsr.FissionResourceNS) sort.Strings(namespaces) return namespaces } func (nsr *NamespaceResolver) FissionNSWithOptions(option ...option) map[string]string { var options options for _, opt := range option { options = *opt(&options) } nsr.mu.RLock() fissionResourceNS := make(map[string]string, len(nsr.FissionResourceNS)) for k, v := range nsr.FissionResourceNS { fissionResourceNS[k] = v } nsr.mu.RUnlock() if options.functionNS && nsr.FunctionNamespace != "" { fissionResourceNS[nsr.FunctionNamespace] = nsr.FunctionNamespace } if options.builderNS && nsr.BuilderNamespace != "" { fissionResourceNS[nsr.BuilderNamespace] = nsr.BuilderNamespace } if options.defaultNs && nsr.DefaultNamespace != "" { fissionResourceNS[nsr.DefaultNamespace] = nsr.DefaultNamespace } if nsr.Logger != nil { nsr.Logger.Debug("fission resource namespaces", zap.Any("namespaces", listNamespaces(fissionResourceNS))) } return fissionResourceNS } // SnapshotWithOptions returns a stable slice copy of Fission namespaces after applying // optional builder/function/default namespace expansion. func (nsr *NamespaceResolver) SnapshotWithOptions(option ...option) []string { namespaces := listNamespaces(nsr.FissionNSWithOptions(option...)) sort.Strings(namespaces) return namespaces } func GetNamespaces() map[string]string { namespaces := make(map[string]string) envValue := os.Getenv(ENV_DEFAULT_NAMESPACE) if len(envValue) > 0 { namespaces[envValue] = envValue } envValue = os.Getenv(ENV_ADDITIONAL_NAMESPACE) if len(envValue) > 0 { lstNamespaces := strings.Split(envValue, ",") for _, namespace := range lstNamespaces { // check to handle string with additional comma at the end of string. eg- ns1,ns2, if namespace != "" { namespaces[namespace] = namespace } } } if len(namespaces) == 0 { namespaces[metav1.NamespaceDefault] = metav1.NamespaceDefault } return namespaces } func (nsr *NamespaceResolver) GetBuilderNS(namespace string) string { if nsr.BuilderNamespace == "" { return namespace } if namespace != metav1.NamespaceDefault { return namespace } return nsr.BuilderNamespace } func (nsr *NamespaceResolver) GetFunctionNS(namespace string) string { if nsr.FunctionNamespace == "" { return namespace } if namespace != metav1.NamespaceDefault { return namespace } return nsr.FunctionNamespace } func (nsr *NamespaceResolver) ResolveNamespace(namespace string) string { if nsr.FunctionNamespace == "" || nsr.BuilderNamespace == "" { return nsr.DefaultNamespace } return namespace } // GetFissionNamespaces => return all fission core component namespaces func DefaultNSResolver() *NamespaceResolver { return nsResolver }