publish configmap with meta info

This commit is contained in:
Safwan
2025-07-15 19:16:26 +05:00
parent 5089955691
commit 8b257a3f0c
9 changed files with 310 additions and 132 deletions

View File

@@ -14,7 +14,6 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"github.com/stakater/Reloader/internal/pkg/controller"
"github.com/stakater/Reloader/internal/pkg/metrics"
@@ -33,27 +32,7 @@ func NewReloaderCommand() *cobra.Command {
}
// options
cmd.PersistentFlags().BoolVar(&options.AutoReloadAll, "auto-reload-all", false, "Auto reload all resources")
cmd.PersistentFlags().StringVar(&options.ConfigmapUpdateOnChangeAnnotation, "configmap-annotation", "configmap.reloader.stakater.com/reload", "annotation to detect changes in configmaps, specified by name")
cmd.PersistentFlags().StringVar(&options.SecretUpdateOnChangeAnnotation, "secret-annotation", "secret.reloader.stakater.com/reload", "annotation to detect changes in secrets, specified by name")
cmd.PersistentFlags().StringVar(&options.ReloaderAutoAnnotation, "auto-annotation", "reloader.stakater.com/auto", "annotation to detect changes in secrets/configmaps")
cmd.PersistentFlags().StringVar(&options.ConfigmapReloaderAutoAnnotation, "configmap-auto-annotation", "configmap.reloader.stakater.com/auto", "annotation to detect changes in configmaps")
cmd.PersistentFlags().StringVar(&options.SecretReloaderAutoAnnotation, "secret-auto-annotation", "secret.reloader.stakater.com/auto", "annotation to detect changes in secrets")
cmd.PersistentFlags().StringVar(&options.AutoSearchAnnotation, "auto-search-annotation", "reloader.stakater.com/search", "annotation to detect changes in configmaps or secrets tagged with special match annotation")
cmd.PersistentFlags().StringVar(&options.SearchMatchAnnotation, "search-match-annotation", "reloader.stakater.com/match", "annotation to mark secrets or configmaps to match the search")
cmd.PersistentFlags().StringVar(&options.LogFormat, "log-format", "", "Log format to use (empty string for text, or JSON)")
cmd.PersistentFlags().StringVar(&options.LogLevel, "log-level", "info", "Log level to use (trace, debug, info, warning, error, fatal and panic)")
cmd.PersistentFlags().StringVar(&options.WebhookUrl, "webhook-url", "", "webhook to trigger instead of performing a reload")
cmd.PersistentFlags().StringSlice("resources-to-ignore", []string{}, "list of resources to ignore (valid options 'configMaps' or 'secrets')")
cmd.PersistentFlags().StringSlice("namespaces-to-ignore", []string{}, "list of namespaces to ignore")
cmd.PersistentFlags().StringSlice("namespace-selector", []string{}, "list of key:value labels to filter on for namespaces")
cmd.PersistentFlags().StringSlice("resource-label-selector", []string{}, "list of key:value labels to filter on for configmaps and secrets")
cmd.PersistentFlags().StringVar(&options.IsArgoRollouts, "is-Argo-Rollouts", "false", "Add support for argo rollouts")
cmd.PersistentFlags().StringVar(&options.ReloadStrategy, constants.ReloadStrategyFlag, constants.EnvVarsReloadStrategy, "Specifies the desired reload strategy")
cmd.PersistentFlags().StringVar(&options.ReloadOnCreate, "reload-on-create", "false", "Add support to watch create events")
cmd.PersistentFlags().StringVar(&options.ReloadOnDelete, "reload-on-delete", "false", "Add support to watch delete events")
cmd.PersistentFlags().BoolVar(&options.EnableHA, "enable-ha", false, "Adds support for running multiple replicas via leadership election")
cmd.PersistentFlags().BoolVar(&options.SyncAfterRestart, "sync-after-restart", false, "Sync add events after reloader restarts")
util.ConfigureReloaderFlags(cmd)
return cmd
}
@@ -140,22 +119,19 @@ func startReloader(cmd *cobra.Command, args []string) {
logrus.Fatal(err)
}
ignoredResourcesList, err := getIgnoredResourcesList(cmd)
ignoredResourcesList, err := util.GetIgnoredResourcesList()
if err != nil {
logrus.Fatal(err)
}
ignoredNamespacesList, err := getIgnoredNamespacesList(cmd)
ignoredNamespacesList := options.NamespacesToIgnore
namespaceLabelSelector, err := util.GetNamespaceLabelSelector()
if err != nil {
logrus.Fatal(err)
}
namespaceLabelSelector, err := getNamespaceLabelSelector(cmd)
if err != nil {
logrus.Fatal(err)
}
resourceLabelSelector, err := getResourceLabelSelector(cmd)
resourceLabelSelector, err := util.GetResourceLabelSelector()
if err != nil {
logrus.Fatal(err)
}
@@ -207,107 +183,8 @@ func startReloader(cmd *cobra.Command, args []string) {
go leadership.RunLeaderElection(lock, ctx, cancel, podName, controllers)
}
util.PublishMetaInfoConfigmap(clientset)
leadership.SetupLivenessEndpoint()
logrus.Fatal(http.ListenAndServe(constants.DefaultHttpListenAddr, nil))
}
func getIgnoredNamespacesList(cmd *cobra.Command) (util.List, error) {
return getStringSliceFromFlags(cmd, "namespaces-to-ignore")
}
func getNamespaceLabelSelector(cmd *cobra.Command) (string, error) {
slice, err := getStringSliceFromFlags(cmd, "namespace-selector")
if err != nil {
logrus.Fatal(err)
}
for i, kv := range slice {
// Legacy support for ":" as a delimiter and "*" for wildcard.
if strings.Contains(kv, ":") {
split := strings.Split(kv, ":")
if split[1] == "*" {
slice[i] = split[0]
} else {
slice[i] = split[0] + "=" + split[1]
}
}
// Convert wildcard to valid apimachinery operator
if strings.Contains(kv, "=") {
split := strings.Split(kv, "=")
if split[1] == "*" {
slice[i] = split[0]
}
}
}
namespaceLabelSelector := strings.Join(slice[:], ",")
_, err = labels.Parse(namespaceLabelSelector)
if err != nil {
logrus.Fatal(err)
}
return namespaceLabelSelector, nil
}
func getResourceLabelSelector(cmd *cobra.Command) (string, error) {
slice, err := getStringSliceFromFlags(cmd, "resource-label-selector")
if err != nil {
logrus.Fatal(err)
}
for i, kv := range slice {
// Legacy support for ":" as a delimiter and "*" for wildcard.
if strings.Contains(kv, ":") {
split := strings.Split(kv, ":")
if split[1] == "*" {
slice[i] = split[0]
} else {
slice[i] = split[0] + "=" + split[1]
}
}
// Convert wildcard to valid apimachinery operator
if strings.Contains(kv, "=") {
split := strings.Split(kv, "=")
if split[1] == "*" {
slice[i] = split[0]
}
}
}
resourceLabelSelector := strings.Join(slice[:], ",")
_, err = labels.Parse(resourceLabelSelector)
if err != nil {
logrus.Fatal(err)
}
return resourceLabelSelector, nil
}
func getStringSliceFromFlags(cmd *cobra.Command, flag string) ([]string, error) {
slice, err := cmd.Flags().GetStringSlice(flag)
if err != nil {
return nil, err
}
return slice, nil
}
func getIgnoredResourcesList(cmd *cobra.Command) (util.List, error) {
ignoredResourcesList, err := getStringSliceFromFlags(cmd, "resources-to-ignore")
if err != nil {
return nil, err
}
for _, v := range ignoredResourcesList {
if v != "configMaps" && v != "secrets" {
return nil, fmt.Errorf("'resources-to-ignore' only accepts 'configMaps' or 'secrets', not '%s'", v)
}
}
if len(ignoredResourcesList) > 1 {
return nil, errors.New("'resources-to-ignore' only accepts 'configMaps' or 'secrets', not both")
}
return ignoredResourcesList, nil
}

View File

@@ -57,6 +57,14 @@ var (
EnableHA = false
// Url to send a request to instead of triggering a reload
WebhookUrl = ""
ResourcesToIgnore = []string{}
NamespacesToIgnore = []string{}
NamespaceSelectors = []string{}
ResourceSelectors = []string{}
)
func ToArgoRolloutStrategy(s string) ArgoRolloutStrategy {

View File

@@ -0,0 +1,89 @@
package util
import (
"runtime/debug"
"github.com/stakater/Reloader/internal/pkg/options"
)
type ReloaderOptions struct {
AutoReloadAll bool `json:"autoReloadAll"`
ConfigmapUpdateOnChangeAnnotation string `json:"configmapUpdateOnChangeAnnotation"`
SecretUpdateOnChangeAnnotation string `json:"secretUpdateOnChangeAnnotation"`
ReloaderAutoAnnotation string `json:"reloaderAutoAnnotation"`
IgnoreResourceAnnotation string `json:"ignoreResourceAnnotation"`
ConfigmapReloaderAutoAnnotation string `json:"configmapReloaderAutoAnnotation"`
SecretReloaderAutoAnnotation string `json:"secretReloaderAutoAnnotation"`
ConfigmapExcludeReloaderAnnotation string `json:"configmapExcludeReloaderAnnotation"`
SecretExcludeReloaderAnnotation string `json:"secretExcludeReloaderAnnotation"`
AutoSearchAnnotation string `json:"autoSearchAnnotation"`
SearchMatchAnnotation string `json:"searchMatchAnnotation"`
RolloutStrategyAnnotation string `json:"rolloutStrategyAnnotation"`
LogFormat string `json:"logFormat"`
LogLevel string `json:"logLevel"`
IsArgoRollouts string `json:"isArgoRollouts"`
ReloadStrategy string `json:"reloadStrategy"`
ReloadOnCreate string `json:"reloadOnCreate"`
ReloadOnDelete string `json:"reloadOnDelete"`
SyncAfterRestart bool `json:"syncAfterRestart"`
EnableHA bool `json:"enableHA"`
WebhookUrl string `json:"webhookUrl"`
}
func GetReloaderOptions() *ReloaderOptions {
return &ReloaderOptions{
AutoReloadAll: options.AutoReloadAll,
ConfigmapUpdateOnChangeAnnotation: options.ConfigmapUpdateOnChangeAnnotation,
SecretUpdateOnChangeAnnotation: options.SecretUpdateOnChangeAnnotation,
ReloaderAutoAnnotation: options.ReloaderAutoAnnotation,
IgnoreResourceAnnotation: options.IgnoreResourceAnnotation,
ConfigmapReloaderAutoAnnotation: options.ConfigmapReloaderAutoAnnotation,
SecretReloaderAutoAnnotation: options.SecretReloaderAutoAnnotation,
ConfigmapExcludeReloaderAnnotation: options.ConfigmapExcludeReloaderAnnotation,
SecretExcludeReloaderAnnotation: options.SecretExcludeReloaderAnnotation,
AutoSearchAnnotation: options.AutoSearchAnnotation,
SearchMatchAnnotation: options.SearchMatchAnnotation,
RolloutStrategyAnnotation: options.RolloutStrategyAnnotation,
LogFormat: options.LogFormat,
LogLevel: options.LogLevel,
IsArgoRollouts: options.IsArgoRollouts,
ReloadStrategy: options.ReloadStrategy,
ReloadOnCreate: options.ReloadOnCreate,
ReloadOnDelete: options.ReloadOnDelete,
SyncAfterRestart: options.SyncAfterRestart,
EnableHA: options.EnableHA,
WebhookUrl: options.WebhookUrl,
}
}
type BuildInfo struct {
GoVersion string `json:"goversion"`
Version string `json:"version"`
Checksum string `json:"checksum"`
VCSRevision string `json:"vcs.revision,omitempty"`
VCSModified string `json:"vcs.modified,omitempty"`
VCSTime string `json:"vcs.time,omitempty"`
}
func parseBuildInfo(info *debug.BuildInfo) *BuildInfo {
infoMap := make(map[string]string)
infoMap["goversion"] = info.GoVersion
infoMap["version"] = info.Main.Version
infoMap["checksum"] = info.Main.Sum
for _, setting := range info.Settings {
if setting.Key == "vcs.revision" || setting.Key == "vcs.time" || setting.Key == "vcs.modified" {
infoMap[setting.Key] = setting.Value
}
}
metaInfo := &BuildInfo{
GoVersion: info.GoVersion,
Version: info.Main.Version,
Checksum: info.Main.Sum,
VCSRevision: infoMap["vcs.revision"],
VCSModified: infoMap["vcs.modified"],
VCSTime: infoMap["vcs.time"],
}
return metaInfo
}

View File

@@ -2,12 +2,25 @@ package util
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"os"
"runtime/debug"
"sort"
"strings"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/stakater/Reloader/internal/pkg/constants"
"github.com/stakater/Reloader/internal/pkg/crypto"
"github.com/stakater/Reloader/internal/pkg/options"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
)
// ConvertToEnvVarName converts the given text into a usable env var
@@ -52,6 +65,69 @@ func GetSHAfromSecret(data map[string][]byte) string {
return crypto.GenerateSHA(strings.Join(values, ";"))
}
func PublishMetaInfoConfigmap(clientset kubernetes.Interface) {
namespace := os.Getenv("RELOADER_NAMESPACE")
if namespace == "" {
logrus.Warn("RELOADER_NAMESPACE is not set, skipping meta info configmap creation")
return
}
info, ok := debug.ReadBuildInfo()
if !ok {
return
}
metaInfoMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "reloader-meta-info",
Namespace: namespace,
Labels: map[string]string{
"reloader.stakater.com/meta-info-for": "reloader-oss",
},
},
Data: map[string]string{},
}
buildInfo := parseBuildInfo(info)
buildInfoJSON, err := json.Marshal(buildInfo)
if err == nil {
metaInfoMap.Data["buildinfo"] = string(buildInfoJSON)
}
reloaderOptions := GetReloaderOptions()
reloaderOptionsJSON, err := json.Marshal(reloaderOptions)
if err == nil {
metaInfoMap.Data["reloaderOptions"] = string(reloaderOptionsJSON)
}
deploymentInfoJson, err := json.Marshal(metav1.ObjectMeta{
Name: os.Getenv("RELOADER_DEPLOYMENT_NAME"),
Namespace: namespace,
})
if err == nil {
metaInfoMap.Data["deploymentInfo"] = string(deploymentInfoJson)
}
if _, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), metaInfoMap.Name, metav1.GetOptions{}); err == nil {
logrus.Info("Meta info configmap already exists, deleting it")
err = clientset.CoreV1().ConfigMaps(namespace).Delete(context.Background(), metaInfoMap.Name, metav1.DeleteOptions{})
if err != nil {
logrus.Warn("Failed to delete existing meta info configmap: ", err)
return
}
logrus.Info("Deleted existing meta info configmap")
}
_, err = clientset.CoreV1().ConfigMaps(namespace).Create(context.Background(), metaInfoMap, metav1.CreateOptions{})
if err != nil {
logrus.Warn("Failed to create meta info configmap: ", err)
}
}
type List []string
type Map map[string]string
@@ -64,3 +140,106 @@ func (l *List) Contains(s string) bool {
}
return false
}
func ConfigureReloaderFlags(cmd *cobra.Command) {
cmd.PersistentFlags().BoolVar(&options.AutoReloadAll, "auto-reload-all", false, "Auto reload all resources")
cmd.PersistentFlags().StringVar(&options.ConfigmapUpdateOnChangeAnnotation, "configmap-annotation", "configmap.reloader.stakater.com/reload", "annotation to detect changes in configmaps, specified by name")
cmd.PersistentFlags().StringVar(&options.SecretUpdateOnChangeAnnotation, "secret-annotation", "secret.reloader.stakater.com/reload", "annotation to detect changes in secrets, specified by name")
cmd.PersistentFlags().StringVar(&options.ReloaderAutoAnnotation, "auto-annotation", "reloader.stakater.com/auto", "annotation to detect changes in secrets/configmaps")
cmd.PersistentFlags().StringVar(&options.ConfigmapReloaderAutoAnnotation, "configmap-auto-annotation", "configmap.reloader.stakater.com/auto", "annotation to detect changes in configmaps")
cmd.PersistentFlags().StringVar(&options.SecretReloaderAutoAnnotation, "secret-auto-annotation", "secret.reloader.stakater.com/auto", "annotation to detect changes in secrets")
cmd.PersistentFlags().StringVar(&options.AutoSearchAnnotation, "auto-search-annotation", "reloader.stakater.com/search", "annotation to detect changes in configmaps or secrets tagged with special match annotation")
cmd.PersistentFlags().StringVar(&options.SearchMatchAnnotation, "search-match-annotation", "reloader.stakater.com/match", "annotation to mark secrets or configmaps to match the search")
cmd.PersistentFlags().StringVar(&options.LogFormat, "log-format", "", "Log format to use (empty string for text, or JSON)")
cmd.PersistentFlags().StringVar(&options.LogLevel, "log-level", "info", "Log level to use (trace, debug, info, warning, error, fatal and panic)")
cmd.PersistentFlags().StringVar(&options.WebhookUrl, "webhook-url", "", "webhook to trigger instead of performing a reload")
cmd.PersistentFlags().StringSliceVar(&options.ResourcesToIgnore, "resources-to-ignore", options.ResourcesToIgnore, "list of resources to ignore (valid options 'configMaps' or 'secrets')")
cmd.PersistentFlags().StringSliceVar(&options.NamespacesToIgnore, "namespaces-to-ignore", options.NamespacesToIgnore, "list of namespaces to ignore")
cmd.PersistentFlags().StringSliceVar(&options.NamespaceSelectors, "namespace-selector", options.NamespaceSelectors, "list of key:value labels to filter on for namespaces")
cmd.PersistentFlags().StringSliceVar(&options.ResourceSelectors, "resource-label-selector", options.ResourceSelectors, "list of key:value labels to filter on for configmaps and secrets")
cmd.PersistentFlags().StringVar(&options.IsArgoRollouts, "is-Argo-Rollouts", "false", "Add support for argo rollouts")
cmd.PersistentFlags().StringVar(&options.ReloadStrategy, constants.ReloadStrategyFlag, constants.EnvVarsReloadStrategy, "Specifies the desired reload strategy")
cmd.PersistentFlags().StringVar(&options.ReloadOnCreate, "reload-on-create", "false", "Add support to watch create events")
cmd.PersistentFlags().StringVar(&options.ReloadOnDelete, "reload-on-delete", "false", "Add support to watch delete events")
cmd.PersistentFlags().BoolVar(&options.EnableHA, "enable-ha", false, "Adds support for running multiple replicas via leadership election")
cmd.PersistentFlags().BoolVar(&options.SyncAfterRestart, "sync-after-restart", false, "Sync add events after reloader restarts")
}
func GetNamespaceLabelSelector() (string, error) {
slice := options.NamespaceSelectors
for i, kv := range slice {
// Legacy support for ":" as a delimiter and "*" for wildcard.
if strings.Contains(kv, ":") {
split := strings.Split(kv, ":")
if split[1] == "*" {
slice[i] = split[0]
} else {
slice[i] = split[0] + "=" + split[1]
}
}
// Convert wildcard to valid apimachinery operator
if strings.Contains(kv, "=") {
split := strings.Split(kv, "=")
if split[1] == "*" {
slice[i] = split[0]
}
}
}
namespaceLabelSelector := strings.Join(slice[:], ",")
_, err := labels.Parse(namespaceLabelSelector)
if err != nil {
logrus.Fatal(err)
}
return namespaceLabelSelector, nil
}
func GetResourceLabelSelector() (string, error) {
slice := options.ResourceSelectors
for i, kv := range slice {
// Legacy support for ":" as a delimiter and "*" for wildcard.
if strings.Contains(kv, ":") {
split := strings.Split(kv, ":")
if split[1] == "*" {
slice[i] = split[0]
} else {
slice[i] = split[0] + "=" + split[1]
}
}
// Convert wildcard to valid apimachinery operator
if strings.Contains(kv, "=") {
split := strings.Split(kv, "=")
if split[1] == "*" {
slice[i] = split[0]
}
}
}
resourceLabelSelector := strings.Join(slice[:], ",")
_, err := labels.Parse(resourceLabelSelector)
if err != nil {
logrus.Fatal(err)
}
return resourceLabelSelector, nil
}
func GetIgnoredResourcesList() (List, error) {
ignoredResourcesList := options.ResourcesToIgnore // getStringSliceFromFlags(cmd, "resources-to-ignore")
for _, v := range ignoredResourcesList {
if v != "configMaps" && v != "secrets" {
return nil, fmt.Errorf("'resources-to-ignore' only accepts 'configMaps' or 'secrets', not '%s'", v)
}
}
if len(ignoredResourcesList) > 1 {
return nil, errors.New("'resources-to-ignore' only accepts 'configMaps' or 'secrets', not both")
}
return ignoredResourcesList, nil
}