func (c *ConfigManager) OnSave(f ConfigReceiver) {
	logger.V(1).Info("calling OnSave hooks")
	c.onSaves = append(c.onSaves, f)
}
OnSave adds a ConfigReceiver function to call when ConfigManager has successfully committed
configuration to storage.
package configmanager
import (
	"context"
	"fmt"
	"sort"
	"sync"
	"time"
	"github.com/pomerium/pomerium-operator/internal/log"
	pomeriumconfig "github.com/pomerium/pomerium/config"
	"gopkg.in/yaml.v2"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
var logger = log.L.WithValues("component", "configmanager")
const configKey = "config.yaml"
type ConfigManager struct {
	namespace    string
	secret       string
	client       client.Client
	mutex        sync.RWMutex
	policyList   map[ResourceIdentifier][]pomeriumconfig.Policy
	baseConfig   []byte
	settleTicker *time.Ticker
	onSaves      []ConfigReceiver
}
func NewConfigManager(namespace string, secret string, client client.Client, settlePeriod time.Duration) *ConfigManager {
	return &ConfigManager{
		namespace:    namespace,
		secret:       secret,
		client:       client,
		policyList:   make(map[ResourceIdentifier][]pomeriumconfig.Policy),
		settleTicker: time.NewTicker(settlePeriod),
	}
}
func (c *ConfigManager) Set(id ResourceIdentifier, policy []pomeriumconfig.Policy) {
	logger.V(1).Info("setting policy for resource", "id", id)
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.policyList[id] = policy
	logger.Info("set policy for resource", "id", id)
}
func (c *ConfigManager) Remove(id ResourceIdentifier) error {
	logger.V(1).Info("removing policy for resource", "id", id)
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if _, ok := c.policyList[id]; !ok {
		logger.V(1).Info("resource not found", "id", id)
		return nil
	}
	delete(c.policyList, id)
	logger.Info("removed policy for resource", "id", id)
	return nil
}
func (c *ConfigManager) Save() error {
	logger.V(1).Info("updating config Secret")
	var tmpOptions pomeriumconfig.Options
	tmpOptions, err := c.GetCurrentConfig()
	if err != nil {
		return fmt.Errorf("could not render current config: %w", err)
	}
	secretObj := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: c.secret, Namespace: c.namespace}}
	op, err := controllerutil.CreateOrUpdate(context.TODO(), c.client, secretObj, func() error {
		configBytes, err := yaml.Marshal(tmpOptions)
		if err != nil {
			return fmt.Errorf("could not serialize config: %w", err)
		}
		secretObj.Data = map[string][]byte{configKey: configBytes}
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to update secret: %w", err)
	}
	logger.WithValues("operation", op).V(1).Info("update config Secret result")
	if op == controllerutil.OperationResultUpdated || op == controllerutil.OperationResultCreated {
		logger.Info("successfully saved Secret")
		c.callOnSaves(tmpOptions)
	}
	return nil
}
func (c *ConfigManager) SetBaseConfig(configBytes []byte) error {
	err := yaml.Unmarshal(configBytes, &pomeriumconfig.Options{})
	if err != nil {
		return fmt.Errorf("failed to unmarshal configuration: %w", err)
	}
	c.baseConfig = configBytes
	return nil
}
func (c *ConfigManager) getBaseConfig() (options pomeriumconfig.Options, err error) {
	err = yaml.Unmarshal(c.baseConfig, &options)
	if err != nil {
		return options, fmt.Errorf("failed to load base config: %w", err)
	}
	return
}
func (c *ConfigManager) GetCurrentConfig() (options pomeriumconfig.Options, err error) {
	c.mutex.RLock()
	defer c.mutex.RUnlock()
	options, err = c.getBaseConfig()
	if err != nil {
		logger.Error(err, "could not load base configuration")
		return options, fmt.Errorf("could not load base configuration: %w", err)
	}
	var policyIds []ResourceIdentifier
	for id := range c.policyList {
		policyIds = append(policyIds, id)
	}
	sort.Slice(policyIds, func(i, j int) bool {
		switch {
		case policyIds[i].NamespacedName.String() < policyIds[j].NamespacedName.String():
			return true
		case policyIds[i].NamespacedName.String() > policyIds[j].NamespacedName.String():
			return false
		}
		return policyIds[i].GVK.String() < policyIds[j].GVK.String()
	})
	for _, id := range policyIds {
		options.Policies = append(options.Policies, c.policyList[id]...)
	}
	return
}
func (c *ConfigManager) GetPersistedConfig() (options pomeriumconfig.Options, err error) {
	secretObj := &corev1.Secret{}
	if err = c.client.Get(context.Background(), types.NamespacedName{Name: c.secret, Namespace: c.namespace}, secretObj); err != nil {
		return options, fmt.Errorf("output secret not found: %w", err)
	}
	if err = yaml.Unmarshal([]byte(secretObj.Data[configKey]), &options); err != nil {
		return options, fmt.Errorf("could not unmarshal config: %w", err)
	}
	return
}
func (c *ConfigManager) Start(ctx context.Context) error {
	for {
		select {
		case <-ctx.Done():
			c.loopSave()
			return nil
		case <-c.settleTicker.C:
			c.loopSave()
		}
	}
}
func (c *ConfigManager) loopSave() {
	err := c.Save()
	if err != nil {
		log.L.Error(err, "failed to save to secret", "secret", c.secret)
	}
}
func (c *ConfigManager) NeedLeaderElection() bool {
	return true
}
type ConfigReceiver func(pomeriumconfig.Options)