From 8b257a3f0c9c3c12c82c01aa54389d467855857c Mon Sep 17 00:00:00 2001 From: Safwan Date: Tue, 15 Jul 2025 19:16:26 +0500 Subject: [PATCH 01/19] publish configmap with meta info --- Dockerfile | 2 +- .../chart/reloader/templates/clusterrole.yaml | 2 + .../chart/reloader/templates/deployment.yaml | 11 ++ .../chart/reloader/templates/role.yaml | 2 + deployments/kubernetes/reloader.yaml | 10 + internal/pkg/cmd/reloader.go | 139 +------------- internal/pkg/options/flags.go | 8 + internal/pkg/util/metainfo.go | 89 +++++++++ internal/pkg/util/util.go | 179 ++++++++++++++++++ 9 files changed, 310 insertions(+), 132 deletions(-) create mode 100644 internal/pkg/util/metainfo.go diff --git a/Dockerfile b/Dockerfile index 57a3999d..41d0238e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,7 @@ RUN CGO_ENABLED=0 \ GOPROXY=${GOPROXY} \ GOPRIVATE=${GOPRIVATE} \ GO111MODULE=on \ - go build -mod=mod -a -o manager main.go + go build -ldflags="-s -w" -installsuffix 'static' -mod=mod -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/deployments/kubernetes/chart/reloader/templates/clusterrole.yaml b/deployments/kubernetes/chart/reloader/templates/clusterrole.yaml index 21fc7566..9906e259 100644 --- a/deployments/kubernetes/chart/reloader/templates/clusterrole.yaml +++ b/deployments/kubernetes/chart/reloader/templates/clusterrole.yaml @@ -31,6 +31,8 @@ rules: - list - get - watch + - create + - delete {{- if .Values.reloader.namespaceSelector }} - apiGroups: - "" diff --git a/deployments/kubernetes/chart/reloader/templates/deployment.yaml b/deployments/kubernetes/chart/reloader/templates/deployment.yaml index 287567b9..0c6ff882 100644 --- a/deployments/kubernetes/chart/reloader/templates/deployment.yaml +++ b/deployments/kubernetes/chart/reloader/templates/deployment.yaml @@ -144,6 +144,17 @@ spec: fieldRef: fieldPath: metadata.namespace {{- end }} + + - name: RELOADER_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + + - name: RELOADER_DEPLOYMENT_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if .Values.reloader.enableHA }} - name: POD_NAME valueFrom: diff --git a/deployments/kubernetes/chart/reloader/templates/role.yaml b/deployments/kubernetes/chart/reloader/templates/role.yaml index a031e3e2..3ee14ae1 100644 --- a/deployments/kubernetes/chart/reloader/templates/role.yaml +++ b/deployments/kubernetes/chart/reloader/templates/role.yaml @@ -32,6 +32,8 @@ rules: - list - get - watch + - create + - delete {{- if and (.Capabilities.APIVersions.Has "apps.openshift.io/v1") (.Values.reloader.isOpenshift) }} - apiGroups: - "apps.openshift.io" diff --git a/deployments/kubernetes/reloader.yaml b/deployments/kubernetes/reloader.yaml index 254420b1..6880bc37 100644 --- a/deployments/kubernetes/reloader.yaml +++ b/deployments/kubernetes/reloader.yaml @@ -104,6 +104,16 @@ spec: resourceFieldRef: divisor: "1" resource: limits.memory + - name: RELOADER_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + + - name: RELOADER_DEPLOYMENT_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + image: "ghcr.io/stakater/reloader:latest" imagePullPolicy: IfNotPresent livenessProbe: diff --git a/internal/pkg/cmd/reloader.go b/internal/pkg/cmd/reloader.go index f0aac834..3a3d83c0 100644 --- a/internal/pkg/cmd/reloader.go +++ b/internal/pkg/cmd/reloader.go @@ -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 -} diff --git a/internal/pkg/options/flags.go b/internal/pkg/options/flags.go index 7c0e14e1..260b052c 100644 --- a/internal/pkg/options/flags.go +++ b/internal/pkg/options/flags.go @@ -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 { diff --git a/internal/pkg/util/metainfo.go b/internal/pkg/util/metainfo.go new file mode 100644 index 00000000..b4e33f0c --- /dev/null +++ b/internal/pkg/util/metainfo.go @@ -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 +} diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index 1a2696d8..fdec73b2 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -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 +} From f69773c588e0f581a439631a3e51d1b8efaf3d11 Mon Sep 17 00:00:00 2001 From: Safwan Date: Tue, 15 Jul 2025 20:08:23 +0500 Subject: [PATCH 02/19] refactoring --- internal/pkg/util/util.go | 43 +++--------- .../pkg/util => pkg/metainfo}/metainfo.go | 67 ++++++++++++++++++- 2 files changed, 75 insertions(+), 35 deletions(-) rename {internal/pkg/util => pkg/metainfo}/metainfo.go (70%) diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index fdec73b2..5322f014 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" "errors" "fmt" "os" @@ -17,6 +16,7 @@ import ( "github.com/stakater/Reloader/internal/pkg/constants" "github.com/stakater/Reloader/internal/pkg/crypto" "github.com/stakater/Reloader/internal/pkg/options" + "github.com/stakater/Reloader/pkg/metainfo" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -78,43 +78,20 @@ func PublishMetaInfoConfigmap(clientset kubernetes.Interface) { return } - metaInfoMap := &v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "reloader-meta-info", + metaInfo := &metainfo.MetaInfo{ + BuildInfo: *metainfo.NewBuildInfo(info), + ReloaderOptions: *metainfo.GetReloaderOptions(), + DeploymentInfo: metav1.ObjectMeta{ + Name: os.Getenv("RELOADER_DEPLOYMENT_NAME"), 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) + configMap := metaInfo.ToConfigMap() - 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 { + if _, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), configMap.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{}) + err = clientset.CoreV1().ConfigMaps(namespace).Delete(context.Background(), configMap.Name, metav1.DeleteOptions{}) if err != nil { logrus.Warn("Failed to delete existing meta info configmap: ", err) return @@ -122,7 +99,7 @@ func PublishMetaInfoConfigmap(clientset kubernetes.Interface) { logrus.Info("Deleted existing meta info configmap") } - _, err = clientset.CoreV1().ConfigMaps(namespace).Create(context.Background(), metaInfoMap, metav1.CreateOptions{}) + _, err := clientset.CoreV1().ConfigMaps(namespace).Create(context.Background(), configMap, metav1.CreateOptions{}) if err != nil { logrus.Warn("Failed to create meta info configmap: ", err) } diff --git a/internal/pkg/util/metainfo.go b/pkg/metainfo/metainfo.go similarity index 70% rename from internal/pkg/util/metainfo.go rename to pkg/metainfo/metainfo.go index b4e33f0c..896f52cc 100644 --- a/internal/pkg/util/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -1,9 +1,18 @@ -package util +package metainfo import ( + "encoding/json" "runtime/debug" "github.com/stakater/Reloader/internal/pkg/options" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + MetaInfoConfigmapName = "reloader-meta-info" + MetaInfoConfigmapLabel = "reloader.stakater.com/meta-info" + MetaInfoConfigmapLabelValue = "reloader-oss" ) type ReloaderOptions struct { @@ -30,6 +39,12 @@ type ReloaderOptions struct { WebhookUrl string `json:"webhookUrl"` } +type MetaInfo struct { + BuildInfo BuildInfo `json:"buildInfo"` + ReloaderOptions ReloaderOptions `json:"reloaderOptions"` + DeploymentInfo metav1.ObjectMeta `json:"deploymentInfo"` +} + func GetReloaderOptions() *ReloaderOptions { return &ReloaderOptions{ AutoReloadAll: options.AutoReloadAll, @@ -65,7 +80,7 @@ type BuildInfo struct { VCSTime string `json:"vcs.time,omitempty"` } -func parseBuildInfo(info *debug.BuildInfo) *BuildInfo { +func NewBuildInfo(info *debug.BuildInfo) *BuildInfo { infoMap := make(map[string]string) infoMap["goversion"] = info.GoVersion infoMap["version"] = info.Main.Version @@ -87,3 +102,51 @@ func parseBuildInfo(info *debug.BuildInfo) *BuildInfo { return metaInfo } + +func (m *MetaInfo) ToConfigMap() *v1.ConfigMap { + return &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: MetaInfoConfigmapName, + Namespace: m.DeploymentInfo.Namespace, + Labels: map[string]string{ + MetaInfoConfigmapLabel: MetaInfoConfigmapLabelValue, + }, + }, + Data: map[string]string{ + "buildInfo": toJson(m.BuildInfo), + "reloaderOptions": toJson(m.ReloaderOptions), + "deploymentInfo": toJson(m.DeploymentInfo), + }, + } +} + +func NewMetaInfo(configmap *v1.ConfigMap) *MetaInfo { + var buildInfo BuildInfo + if val, ok := configmap.Data["buildInfo"]; ok { + json.Unmarshal([]byte(val), &buildInfo) + } + + var reloaderOptions ReloaderOptions + if val, ok := configmap.Data["reloaderOptions"]; ok { + json.Unmarshal([]byte(val), &reloaderOptions) + } + + var deploymentInfo metav1.ObjectMeta + if val, ok := configmap.Data["deploymentInfo"]; ok { + json.Unmarshal([]byte(val), &deploymentInfo) + } + + return &MetaInfo{ + BuildInfo: buildInfo, + ReloaderOptions: reloaderOptions, + DeploymentInfo: deploymentInfo, + } +} + +func toJson(data interface{}) string { + jsonData, err := json.Marshal(data) + if err != nil { + return "" + } + return string(jsonData) +} From 85bd7a075c46a8730d01c3c138d5823bca99c59a Mon Sep 17 00:00:00 2001 From: Safwan Date: Wed, 16 Jul 2025 18:08:24 +0500 Subject: [PATCH 03/19] updated docker file --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 41d0238e..a26d90e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,7 @@ RUN CGO_ENABLED=0 \ GOPROXY=${GOPROXY} \ GOPRIVATE=${GOPRIVATE} \ GO111MODULE=on \ - go build -ldflags="-s -w" -installsuffix 'static' -mod=mod -a -o manager main.go + go build -ldflags="-s -w" -installsuffix 'static' -mod=mod -a -o manager ./ # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details From 01f62cf823a18534127c0db7b1694424172acf0e Mon Sep 17 00:00:00 2001 From: Safwan Date: Wed, 16 Jul 2025 18:29:26 +0500 Subject: [PATCH 04/19] renamed struct fields --- pkg/metainfo/metainfo.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/metainfo/metainfo.go b/pkg/metainfo/metainfo.go index 896f52cc..8e9009b8 100644 --- a/pkg/metainfo/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -72,12 +72,12 @@ func GetReloaderOptions() *ReloaderOptions { } 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"` + GoVersion string `json:"goversion"` + Version string `json:"version"` + Checksum string `json:"checksum"` + CommitHash string `json:"commitHash,omitempty"` + IsDirty string `json:"isDirty,omitempty"` + CommitTime string `json:"commitTime,omitempty"` } func NewBuildInfo(info *debug.BuildInfo) *BuildInfo { @@ -92,12 +92,12 @@ func NewBuildInfo(info *debug.BuildInfo) *BuildInfo { } } 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"], + GoVersion: info.GoVersion, + Version: info.Main.Version, + Checksum: info.Main.Sum, + CommitHash: infoMap["vcs.revision"], + IsDirty: infoMap["vcs.modified"], + CommitTime: infoMap["vcs.time"], } return metaInfo From cf600f776130f00dcefaf78df9b03aeb6ec5f71e Mon Sep 17 00:00:00 2001 From: Safwan Date: Wed, 16 Jul 2025 19:37:02 +0500 Subject: [PATCH 05/19] type safety where applicable in meta info --- pkg/metainfo/metainfo.go | 91 +++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/pkg/metainfo/metainfo.go b/pkg/metainfo/metainfo.go index 8e9009b8..2a234a69 100644 --- a/pkg/metainfo/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -3,6 +3,8 @@ package metainfo import ( "encoding/json" "runtime/debug" + "strconv" + "time" "github.com/stakater/Reloader/internal/pkg/options" v1 "k8s.io/api/core/v1" @@ -16,27 +18,31 @@ const ( ) 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"` + 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"` + ResourcesToIgnore []string `json:"resourcesToIgnore"` + NamespaceSelectors []string `json:"namespaceSelectors"` + ResourceSelectors []string `json:"resourceSelectors"` + NamespacesToIgnore []string `json:"namespacesToIgnore"` } type MetaInfo struct { @@ -68,16 +74,20 @@ func GetReloaderOptions() *ReloaderOptions { SyncAfterRestart: options.SyncAfterRestart, EnableHA: options.EnableHA, WebhookUrl: options.WebhookUrl, + ResourcesToIgnore: options.ResourcesToIgnore, + NamespaceSelectors: options.NamespaceSelectors, + ResourceSelectors: options.ResourceSelectors, + NamespacesToIgnore: options.NamespacesToIgnore, } } type BuildInfo struct { - GoVersion string `json:"goversion"` - Version string `json:"version"` - Checksum string `json:"checksum"` - CommitHash string `json:"commitHash,omitempty"` - IsDirty string `json:"isDirty,omitempty"` - CommitTime string `json:"commitTime,omitempty"` + GoVersion string `json:"goversion"` + Version string `json:"version"` + Checksum string `json:"checksum"` + CommitHash string `json:"commitHash"` + IsDirty bool `json:"isDirty"` + CommitTime time.Time `json:"commitTime"` } func NewBuildInfo(info *debug.BuildInfo) *BuildInfo { @@ -91,13 +101,14 @@ func NewBuildInfo(info *debug.BuildInfo) *BuildInfo { infoMap[setting.Key] = setting.Value } } + metaInfo := &BuildInfo{ GoVersion: info.GoVersion, Version: info.Main.Version, Checksum: info.Main.Sum, CommitHash: infoMap["vcs.revision"], - IsDirty: infoMap["vcs.modified"], - CommitTime: infoMap["vcs.time"], + IsDirty: parseBool(infoMap["vcs.modified"]), + CommitTime: parseTime(infoMap["vcs.time"]), } return metaInfo @@ -150,3 +161,25 @@ func toJson(data interface{}) string { } return string(jsonData) } + +func parseBool(value string) bool { + if value == "" { + return false + } + result, err := strconv.ParseBool(value) + if err != nil { + return false // Default to false if parsing fails + } + return result +} + +func parseTime(value string) time.Time { + if value == "" { + return time.Time{} // Return zero time if value is empty + } + t, err := time.Parse(time.RFC3339, value) + if err != nil { + return time.Time{} // Return zero time if parsing fails + } + return t +} From 2d2c35fcf41a02452bc5895a18e0fc18280e710b Mon Sep 17 00:00:00 2001 From: Safwan Date: Wed, 16 Jul 2025 20:09:19 +0500 Subject: [PATCH 06/19] discard error --- pkg/metainfo/metainfo.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/metainfo/metainfo.go b/pkg/metainfo/metainfo.go index 2a234a69..125b814b 100644 --- a/pkg/metainfo/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -134,17 +134,17 @@ func (m *MetaInfo) ToConfigMap() *v1.ConfigMap { func NewMetaInfo(configmap *v1.ConfigMap) *MetaInfo { var buildInfo BuildInfo if val, ok := configmap.Data["buildInfo"]; ok { - json.Unmarshal([]byte(val), &buildInfo) + _ = json.Unmarshal([]byte(val), &buildInfo) } var reloaderOptions ReloaderOptions if val, ok := configmap.Data["reloaderOptions"]; ok { - json.Unmarshal([]byte(val), &reloaderOptions) + _ = json.Unmarshal([]byte(val), &reloaderOptions) } var deploymentInfo metav1.ObjectMeta if val, ok := configmap.Data["deploymentInfo"]; ok { - json.Unmarshal([]byte(val), &deploymentInfo) + _ = json.Unmarshal([]byte(val), &deploymentInfo) } return &MetaInfo{ From d22a0f25de6dbd7c572cce9a363c9c06ee3df1d8 Mon Sep 17 00:00:00 2001 From: Safwan Date: Thu, 17 Jul 2025 13:03:42 +0500 Subject: [PATCH 07/19] changed types to bool where needed --- pkg/metainfo/metainfo.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/metainfo/metainfo.go b/pkg/metainfo/metainfo.go index 125b814b..34f218a6 100644 --- a/pkg/metainfo/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -32,10 +32,10 @@ type ReloaderOptions struct { RolloutStrategyAnnotation string `json:"rolloutStrategyAnnotation"` LogFormat string `json:"logFormat"` LogLevel string `json:"logLevel"` - IsArgoRollouts string `json:"isArgoRollouts"` + IsArgoRollouts bool `json:"isArgoRollouts"` ReloadStrategy string `json:"reloadStrategy"` - ReloadOnCreate string `json:"reloadOnCreate"` - ReloadOnDelete string `json:"reloadOnDelete"` + ReloadOnCreate bool `json:"reloadOnCreate"` + ReloadOnDelete bool `json:"reloadOnDelete"` SyncAfterRestart bool `json:"syncAfterRestart"` EnableHA bool `json:"enableHA"` WebhookUrl string `json:"webhookUrl"` @@ -67,10 +67,10 @@ func GetReloaderOptions() *ReloaderOptions { RolloutStrategyAnnotation: options.RolloutStrategyAnnotation, LogFormat: options.LogFormat, LogLevel: options.LogLevel, - IsArgoRollouts: options.IsArgoRollouts, + IsArgoRollouts: parseBool(options.IsArgoRollouts), ReloadStrategy: options.ReloadStrategy, - ReloadOnCreate: options.ReloadOnCreate, - ReloadOnDelete: options.ReloadOnDelete, + ReloadOnCreate: parseBool(options.ReloadOnCreate), + ReloadOnDelete: parseBool(options.ReloadOnDelete), SyncAfterRestart: options.SyncAfterRestart, EnableHA: options.EnableHA, WebhookUrl: options.WebhookUrl, From 95cea97d340dca0d689f3ff3b86d873e0e944e3b Mon Sep 17 00:00:00 2001 From: Safwan Date: Thu, 17 Jul 2025 13:04:47 +0500 Subject: [PATCH 08/19] make parsetime exportable --- pkg/metainfo/metainfo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/metainfo/metainfo.go b/pkg/metainfo/metainfo.go index 34f218a6..1897863c 100644 --- a/pkg/metainfo/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -173,7 +173,7 @@ func parseBool(value string) bool { return result } -func parseTime(value string) time.Time { +func ParseTime(value string) time.Time { if value == "" { return time.Time{} // Return zero time if value is empty } From 19918e6fa8dde90b16eb6af2bf2bb40080b965a7 Mon Sep 17 00:00:00 2001 From: Safwan Date: Thu, 17 Jul 2025 13:05:40 +0500 Subject: [PATCH 09/19] fix error --- pkg/metainfo/metainfo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/metainfo/metainfo.go b/pkg/metainfo/metainfo.go index 1897863c..a3dece85 100644 --- a/pkg/metainfo/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -108,7 +108,7 @@ func NewBuildInfo(info *debug.BuildInfo) *BuildInfo { Checksum: info.Main.Sum, CommitHash: infoMap["vcs.revision"], IsDirty: parseBool(infoMap["vcs.modified"]), - CommitTime: parseTime(infoMap["vcs.time"]), + CommitTime: ParseTime(infoMap["vcs.time"]), } return metaInfo From 1e1f094516d1d4e9ea262e7fd2d9d5e5a3f51f30 Mon Sep 17 00:00:00 2001 From: Safwan Date: Fri, 18 Jul 2025 19:27:26 +0500 Subject: [PATCH 10/19] resolved comments and stuff --- Dockerfile | 11 +- .../chart/reloader/templates/clusterrole.yaml | 2 - .../chart/reloader/templates/deployment.yaml | 4 +- .../chart/reloader/templates/role.yaml | 35 ++++ .../chart/reloader/templates/rolebinding.yaml | 31 ++++ .../kubernetes/chart/reloader/values.yaml | 2 +- deployments/kubernetes/reloader.yaml | 35 +++- internal/pkg/util/util.go | 18 +- pkg/metainfo/metainfo.go | 166 +++++++++++------- 9 files changed, 221 insertions(+), 83 deletions(-) diff --git a/Dockerfile b/Dockerfile index a26d90e3..75d292cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,12 @@ ARG TARGETARCH ARG GOPROXY ARG GOPRIVATE +ARG COMMIT +ARG VERSION +ARG BUILD_DATE +ARG ISDIRTY + + WORKDIR /workspace # Copy the Go Modules manifests @@ -30,7 +36,10 @@ RUN CGO_ENABLED=0 \ GOPROXY=${GOPROXY} \ GOPRIVATE=${GOPRIVATE} \ GO111MODULE=on \ - go build -ldflags="-s -w" -installsuffix 'static' -mod=mod -a -o manager ./ + go build -ldflags="-s -w -X github.com/stakater/Reloader/pkg/metainfo.Version=${VERSION} \ + -X github.com/stakater/Reloader/pkg/metainfo.Commit=${COMMIT} \ + -X github.com/stakater/Reloader/pkg/metainfo.BuildDate=${BUILD_DATE} \ + -X github.com/stakater/Reloader/pkg/metainfo.IsDirty=${ISDIRTY}" -installsuffix 'static' -mod=mod -a -o manager ./ # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/deployments/kubernetes/chart/reloader/templates/clusterrole.yaml b/deployments/kubernetes/chart/reloader/templates/clusterrole.yaml index 034370ba..9f655aa9 100644 --- a/deployments/kubernetes/chart/reloader/templates/clusterrole.yaml +++ b/deployments/kubernetes/chart/reloader/templates/clusterrole.yaml @@ -31,8 +31,6 @@ rules: - list - get - watch - - create - - delete {{- if (include "reloader-namespaceSelector" .) }} - apiGroups: - "" diff --git a/deployments/kubernetes/chart/reloader/templates/deployment.yaml b/deployments/kubernetes/chart/reloader/templates/deployment.yaml index cad90e14..0dab7e50 100644 --- a/deployments/kubernetes/chart/reloader/templates/deployment.yaml +++ b/deployments/kubernetes/chart/reloader/templates/deployment.yaml @@ -151,9 +151,7 @@ spec: fieldPath: metadata.namespace - name: RELOADER_DEPLOYMENT_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name + value: {{ template "reloader-fullname" . }} {{- if .Values.reloader.enableHA }} - name: POD_NAME diff --git a/deployments/kubernetes/chart/reloader/templates/role.yaml b/deployments/kubernetes/chart/reloader/templates/role.yaml index 3ee14ae1..dfb4c2c5 100644 --- a/deployments/kubernetes/chart/reloader/templates/role.yaml +++ b/deployments/kubernetes/chart/reloader/templates/role.yaml @@ -103,3 +103,38 @@ rules: - create - patch {{- end }} + +--- + +{{- if .Values.reloader.rbac.enabled }} +{{- if (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }} +apiVersion: rbac.authorization.k8s.io/v1 +{{ else }} +apiVersion: rbac.authorization.k8s.io/v1beta1 +{{- end }} +kind: Role +metadata: + annotations: +{{ include "reloader-helm3.annotations" . | indent 4 }} + labels: +{{ include "reloader-labels.chart" . | indent 4 }} +{{- if .Values.reloader.rbac.labels }} +{{ tpl (toYaml .Values.reloader.rbac.labels) . | indent 4 }} +{{- end }} +{{- if .Values.reloader.matchLabels }} +{{ tpl (toYaml .Values.reloader.matchLabels) . | indent 4 }} +{{- end }} + name: {{ template "reloader-fullname" . }}-metadata-role + namespace: {{ .Values.namespace | default .Release.Namespace }} +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - list + - get + - watch + - create + - update +{{- end }} \ No newline at end of file diff --git a/deployments/kubernetes/chart/reloader/templates/rolebinding.yaml b/deployments/kubernetes/chart/reloader/templates/rolebinding.yaml index dccff31a..b1c2b05b 100644 --- a/deployments/kubernetes/chart/reloader/templates/rolebinding.yaml +++ b/deployments/kubernetes/chart/reloader/templates/rolebinding.yaml @@ -27,3 +27,34 @@ subjects: name: {{ template "reloader-serviceAccountName" . }} namespace: {{ .Values.namespace | default .Release.Namespace }} {{- end }} + +--- +{{- if .Values.reloader.rbac.enabled }} +{{- if (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }} +apiVersion: rbac.authorization.k8s.io/v1 +{{ else }} +apiVersion: rbac.authorization.k8s.io/v1beta1 +{{- end }} +kind: RoleBinding +metadata: + annotations: +{{ include "reloader-helm3.annotations" . | indent 4 }} + labels: +{{ include "reloader-labels.chart" . | indent 4 }} +{{- if .Values.reloader.rbac.labels }} +{{ tpl (toYaml .Values.reloader.rbac.labels) . | indent 4 }} +{{- end }} +{{- if .Values.reloader.matchLabels }} +{{ tpl (toYaml .Values.reloader.matchLabels) . | indent 4 }} +{{- end }} + name: {{ template "reloader-fullname" . }}-metadata-role-binding + namespace: {{ .Values.namespace | default .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "reloader-fullname" . }}-metadata-role +subjects: + - kind: ServiceAccount + name: {{ template "reloader-serviceAccountName" . }} + namespace: {{ .Values.namespace | default .Release.Namespace }} +{{- end }} \ No newline at end of file diff --git a/deployments/kubernetes/chart/reloader/values.yaml b/deployments/kubernetes/chart/reloader/values.yaml index 21c11a3e..a6e79d16 100644 --- a/deployments/kubernetes/chart/reloader/values.yaml +++ b/deployments/kubernetes/chart/reloader/values.yaml @@ -17,7 +17,7 @@ fullnameOverride: "" image: name: stakater/reloader repository: ghcr.io/stakater/reloader - tag: v1.4.5 + tag: test-image # digest: sha256:1234567 pullPolicy: IfNotPresent diff --git a/deployments/kubernetes/reloader.yaml b/deployments/kubernetes/reloader.yaml index 6880bc37..1222d56c 100644 --- a/deployments/kubernetes/reloader.yaml +++ b/deployments/kubernetes/reloader.yaml @@ -76,6 +76,37 @@ subjects: name: reloader-reloader namespace: default --- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: reloader-metadata-role + namespace: default +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - list + - get + - watch + - create + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: reloader-metadata-role-binding + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: reloader-metadata-role +subjects: + - kind: ServiceAccount + name: reloader-reloader + namespace: default +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -110,9 +141,7 @@ spec: fieldPath: metadata.namespace - name: RELOADER_DEPLOYMENT_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name + value: default image: "ghcr.io/stakater/reloader:latest" imagePullPolicy: IfNotPresent diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index 5322f014..74455faf 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "os" - "runtime/debug" "sort" "strings" @@ -72,14 +71,8 @@ func PublishMetaInfoConfigmap(clientset kubernetes.Interface) { return } - info, ok := debug.ReadBuildInfo() - - if !ok { - return - } - metaInfo := &metainfo.MetaInfo{ - BuildInfo: *metainfo.NewBuildInfo(info), + BuildInfo: *metainfo.NewBuildInfo(), ReloaderOptions: *metainfo.GetReloaderOptions(), DeploymentInfo: metav1.ObjectMeta{ Name: os.Getenv("RELOADER_DEPLOYMENT_NAME"), @@ -90,13 +83,12 @@ func PublishMetaInfoConfigmap(clientset kubernetes.Interface) { configMap := metaInfo.ToConfigMap() if _, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), configMap.Name, metav1.GetOptions{}); err == nil { - logrus.Info("Meta info configmap already exists, deleting it") - err = clientset.CoreV1().ConfigMaps(namespace).Delete(context.Background(), configMap.Name, metav1.DeleteOptions{}) + logrus.Info("Meta info configmap already exists, updating it") + _, err = clientset.CoreV1().ConfigMaps(namespace).Update(context.Background(), configMap, metav1.UpdateOptions{}) if err != nil { - logrus.Warn("Failed to delete existing meta info configmap: ", err) - return + logrus.Warn("Failed to update existing meta info configmap: ", err) } - logrus.Info("Deleted existing meta info configmap") + return } _, err := clientset.CoreV1().ConfigMaps(namespace).Create(context.Background(), configMap, metav1.CreateOptions{}) diff --git a/pkg/metainfo/metainfo.go b/pkg/metainfo/metainfo.go index a3dece85..54b0be81 100644 --- a/pkg/metainfo/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -2,7 +2,8 @@ package metainfo import ( "encoding/json" - "runtime/debug" + "fmt" + "runtime" "strconv" "time" @@ -11,44 +12,86 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// Version, Commit, BuildDate, and IsDirty are set during the build process +// using the -X linker flag to inject these values into the binary. +// They provide metadata about the build version, commit hash, build date, and whether there are +// uncommitted changes in the source code at the time of build. +// This information is useful for debugging and tracking the specific build of the Reloader binary. +var Version = "dev" +var Commit = "unknown" +var BuildDate = "unknown" +var IsDirty = "false" + const ( MetaInfoConfigmapName = "reloader-meta-info" - MetaInfoConfigmapLabel = "reloader.stakater.com/meta-info" + MetaInfoConfigmapLabelKey = "reloader.stakater.com/meta-info" MetaInfoConfigmapLabelValue = "reloader-oss" ) +// ReloaderOptions contains all configurable options for the Reloader controller. +// These options control how Reloader behaves when watching for changes in ConfigMaps and Secrets. 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 bool `json:"isArgoRollouts"` - ReloadStrategy string `json:"reloadStrategy"` - ReloadOnCreate bool `json:"reloadOnCreate"` - ReloadOnDelete bool `json:"reloadOnDelete"` - SyncAfterRestart bool `json:"syncAfterRestart"` - EnableHA bool `json:"enableHA"` - WebhookUrl string `json:"webhookUrl"` - ResourcesToIgnore []string `json:"resourcesToIgnore"` - NamespaceSelectors []string `json:"namespaceSelectors"` - ResourceSelectors []string `json:"resourceSelectors"` - NamespacesToIgnore []string `json:"namespacesToIgnore"` + // AutoReloadAll enables automatic reloading of all resources when their corresponding ConfigMaps/Secrets are updated + AutoReloadAll bool `json:"autoReloadAll"` + // ConfigmapUpdateOnChangeAnnotation is the annotation key used to detect changes in ConfigMaps specified by name + ConfigmapUpdateOnChangeAnnotation string `json:"configmapUpdateOnChangeAnnotation"` + // SecretUpdateOnChangeAnnotation is the annotation key used to detect changes in Secrets specified by name + SecretUpdateOnChangeAnnotation string `json:"secretUpdateOnChangeAnnotation"` + // ReloaderAutoAnnotation is the annotation key used to detect changes in any referenced ConfigMaps or Secrets + ReloaderAutoAnnotation string `json:"reloaderAutoAnnotation"` + // IgnoreResourceAnnotation is the annotation key used to ignore resources from being watched + IgnoreResourceAnnotation string `json:"ignoreResourceAnnotation"` + // ConfigmapReloaderAutoAnnotation is the annotation key used to detect changes in ConfigMaps only + ConfigmapReloaderAutoAnnotation string `json:"configmapReloaderAutoAnnotation"` + // SecretReloaderAutoAnnotation is the annotation key used to detect changes in Secrets only + SecretReloaderAutoAnnotation string `json:"secretReloaderAutoAnnotation"` + // ConfigmapExcludeReloaderAnnotation is the annotation key containing comma-separated list of ConfigMaps to exclude from watching + ConfigmapExcludeReloaderAnnotation string `json:"configmapExcludeReloaderAnnotation"` + // SecretExcludeReloaderAnnotation is the annotation key containing comma-separated list of Secrets to exclude from watching + SecretExcludeReloaderAnnotation string `json:"secretExcludeReloaderAnnotation"` + // AutoSearchAnnotation is the annotation key used to detect changes in ConfigMaps/Secrets tagged with SearchMatchAnnotation + AutoSearchAnnotation string `json:"autoSearchAnnotation"` + // SearchMatchAnnotation is the annotation key used to tag ConfigMaps/Secrets to be found by AutoSearchAnnotation + SearchMatchAnnotation string `json:"searchMatchAnnotation"` + // RolloutStrategyAnnotation is the annotation key used to define the rollout update strategy for workloads + RolloutStrategyAnnotation string `json:"rolloutStrategyAnnotation"` + // LogFormat specifies the log format to use (json, or empty string for default text format) + LogFormat string `json:"logFormat"` + // LogLevel specifies the log level to use (trace, debug, info, warning, error, fatal, panic) + LogLevel string `json:"logLevel"` + // IsArgoRollouts indicates whether support for Argo Rollouts is enabled + IsArgoRollouts bool `json:"isArgoRollouts"` + // ReloadStrategy specifies the strategy used to trigger resource reloads (env-vars or annotations) + ReloadStrategy string `json:"reloadStrategy"` + // ReloadOnCreate indicates whether to trigger reloads when ConfigMaps/Secrets are created + ReloadOnCreate bool `json:"reloadOnCreate"` + // ReloadOnDelete indicates whether to trigger reloads when ConfigMaps/Secrets are deleted + ReloadOnDelete bool `json:"reloadOnDelete"` + // SyncAfterRestart indicates whether to sync add events after Reloader restarts (only works when ReloadOnCreate is true) + SyncAfterRestart bool `json:"syncAfterRestart"` + // EnableHA indicates whether High Availability mode is enabled with leader election + EnableHA bool `json:"enableHA"` + // WebhookUrl is the URL to send webhook notifications to instead of performing reloads + WebhookUrl string `json:"webhookUrl"` + // ResourcesToIgnore is a list of resource types to ignore (e.g., "configmaps" or "secrets") + ResourcesToIgnore []string `json:"resourcesToIgnore"` + // NamespaceSelectors is a list of label selectors to filter namespaces to watch + NamespaceSelectors []string `json:"namespaceSelectors"` + // ResourceSelectors is a list of label selectors to filter ConfigMaps and Secrets to watch + ResourceSelectors []string `json:"resourceSelectors"` + // NamespacesToIgnore is a list of namespace names to ignore when watching for changes + NamespacesToIgnore []string `json:"namespacesToIgnore"` } +// MetaInfo contains comprehensive metadata about the Reloader instance. +// This includes build information, configuration options, and deployment details. type MetaInfo struct { - BuildInfo BuildInfo `json:"buildInfo"` - ReloaderOptions ReloaderOptions `json:"reloaderOptions"` - DeploymentInfo metav1.ObjectMeta `json:"deploymentInfo"` + // BuildInfo contains information about the build version, commit, and compilation details + BuildInfo BuildInfo `json:"buildInfo"` + // ReloaderOptions contains all the configuration options and flags used by this Reloader instance + ReloaderOptions ReloaderOptions `json:"reloaderOptions"` + // DeploymentInfo contains metadata about the Kubernetes deployment of this Reloader instance + DeploymentInfo metav1.ObjectMeta `json:"deploymentInfo"` } func GetReloaderOptions() *ReloaderOptions { @@ -81,34 +124,28 @@ func GetReloaderOptions() *ReloaderOptions { } } +// BuildInfo contains information about the build and version of the Reloader binary. +// This includes Go version, release version, commit details, and build timestamp. type BuildInfo struct { - GoVersion string `json:"goversion"` - Version string `json:"version"` - Checksum string `json:"checksum"` - CommitHash string `json:"commitHash"` - IsDirty bool `json:"isDirty"` + // GoVersion is the version of Go used to compile the binary + GoVersion string `json:"goVersion"` + // ReleaseVersion is the version tag or branch of the Reloader release + ReleaseVersion string `json:"releaseVersion"` + // CommitHash is the Git commit hash of the source code used to build this binary + CommitHash string `json:"commitHash"` + // IsDirty indicates whether the working directory had uncommitted changes when built + IsDirty bool `json:"isDirty"` + // CommitTime is the timestamp of the Git commit used to build this binary CommitTime time.Time `json:"commitTime"` } -func NewBuildInfo(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 - } - } - +func NewBuildInfo() *BuildInfo { metaInfo := &BuildInfo{ - GoVersion: info.GoVersion, - Version: info.Main.Version, - Checksum: info.Main.Sum, - CommitHash: infoMap["vcs.revision"], - IsDirty: parseBool(infoMap["vcs.modified"]), - CommitTime: ParseTime(infoMap["vcs.time"]), + GoVersion: runtime.Version(), + ReleaseVersion: Version, + CommitHash: Commit, + IsDirty: parseBool(IsDirty), + CommitTime: ParseUTCTime(BuildDate), } return metaInfo @@ -120,7 +157,7 @@ func (m *MetaInfo) ToConfigMap() *v1.ConfigMap { Name: MetaInfoConfigmapName, Namespace: m.DeploymentInfo.Namespace, Labels: map[string]string{ - MetaInfoConfigmapLabel: MetaInfoConfigmapLabelValue, + MetaInfoConfigmapLabelKey: MetaInfoConfigmapLabelValue, }, }, Data: map[string]string{ @@ -131,27 +168,36 @@ func (m *MetaInfo) ToConfigMap() *v1.ConfigMap { } } -func NewMetaInfo(configmap *v1.ConfigMap) *MetaInfo { +func NewMetaInfo(configmap *v1.ConfigMap) (*MetaInfo, error) { var buildInfo BuildInfo if val, ok := configmap.Data["buildInfo"]; ok { - _ = json.Unmarshal([]byte(val), &buildInfo) + err := json.Unmarshal([]byte(val), &buildInfo) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal buildInfo: %w", err) + } } var reloaderOptions ReloaderOptions if val, ok := configmap.Data["reloaderOptions"]; ok { - _ = json.Unmarshal([]byte(val), &reloaderOptions) + err := json.Unmarshal([]byte(val), &reloaderOptions) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal reloaderOptions: %w", err) + } } var deploymentInfo metav1.ObjectMeta if val, ok := configmap.Data["deploymentInfo"]; ok { - _ = json.Unmarshal([]byte(val), &deploymentInfo) + err := json.Unmarshal([]byte(val), &deploymentInfo) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal deploymentInfo: %w", err) + } } return &MetaInfo{ BuildInfo: buildInfo, ReloaderOptions: reloaderOptions, DeploymentInfo: deploymentInfo, - } + }, nil } func toJson(data interface{}) string { @@ -173,7 +219,7 @@ func parseBool(value string) bool { return result } -func ParseTime(value string) time.Time { +func ParseUTCTime(value string) time.Time { if value == "" { return time.Time{} // Return zero time if value is empty } From dec3410b7f5948d1d671ad61381b8acdf82bb689 Mon Sep 17 00:00:00 2001 From: Safwan Date: Mon, 21 Jul 2025 12:58:11 +0500 Subject: [PATCH 11/19] updated work flows --- .github/workflows/pull_request.yaml | 9 +++++++-- .github/workflows/push.yaml | 10 +++++++++- .github/workflows/release.yaml | 12 ++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 1b005a16..b38e160e 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -134,8 +134,13 @@ jobs: context: . file: ${{ env.DOCKER_FILE_PATH }} pull: true - push: false - build-args: BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }} + push: true + build-args: | + VERSION=merge-${{ github.event.number }} + COMMIT=${{ github.sha }} + BUILD_DATE=${{ steps.prep.outputs.created }} + BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }} + cache-to: type=inline platforms: linux/amd64,linux/arm,linux/arm64 tags: | diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 5fa6204d..814277f2 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -91,6 +91,10 @@ jobs: with: username: ${{ secrets.STAKATER_DOCKERHUB_USERNAME }} password: ${{ secrets.STAKATER_DOCKERHUB_PASSWORD }} + + - name: Create timestamp + id: prep + run: echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT - name: Generate image repository path for Docker registry run: | @@ -148,7 +152,11 @@ jobs: file: ${{ env.DOCKER_FILE_PATH }} pull: true push: true - build-args: BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }} + build-args: | + VERSION=merge-${{ github.event.number }} + COMMIT=${{ github.sha }} + BUILD_DATE=${{ steps.prep.outputs.created }} + BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }} cache-to: type=inline platforms: linux/amd64,linux/arm,linux/arm64 tags: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 775fa080..71d63e5f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -79,6 +79,10 @@ jobs: id: generate_tag run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + - name: Create timestamp + id: prep + run: echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -106,6 +110,10 @@ jobs: platforms: linux/amd64,linux/arm,linux/arm64 tags: | ${{ env.DOCKER_IMAGE_REPOSITORY }}:${{ steps.generate_tag.outputs.RELEASE_VERSION }} + build-args: | + VERSION=${{ steps.generate_tag.outputs.RELEASE_VERSION }} + COMMIT=${{ github.sha }} + BUILD_DATE=${{ steps.prep.outputs.created }} labels: | org.opencontainers.image.source=${{ github.event.repository.clone_url }} org.opencontainers.image.created=${{ steps.prep.outputs.created }} @@ -152,6 +160,10 @@ jobs: platforms: linux/amd64,linux/arm,linux/arm64 tags: | ${{ env.GHCR_IMAGE_REPOSITORY }}:${{ steps.generate_tag.outputs.RELEASE_VERSION }},${{ env.GHCR_IMAGE_REPOSITORY }}:latest + build-args: | + VERSION=${{ steps.generate_tag.outputs.RELEASE_VERSION }} + COMMIT=${{ github.sha }} + BUILD_DATE=${{ steps.prep.outputs.created }} labels: | org.opencontainers.image.source=${{ github.event.repository.clone_url }} org.opencontainers.image.created=${{ steps.prep.outputs.created }} From e311fe2fffea09608f3e011a1646e53ed819efe9 Mon Sep 17 00:00:00 2001 From: Safwan Date: Mon, 21 Jul 2025 13:05:25 +0500 Subject: [PATCH 12/19] updated pull request workflow --- .github/workflows/pull_request.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index b38e160e..a2447f67 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -62,6 +62,10 @@ jobs: go-version-file: 'go.mod' check-latest: true cache: true + - name: Create timestamp + id: prep + run: echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + # Get highest tag and remove any suffixes with '-' - name: Get Highest tag @@ -104,6 +108,7 @@ jobs: kind create cluster kubectl cluster-info + - name: Test run: make test @@ -136,7 +141,7 @@ jobs: pull: true push: true build-args: | - VERSION=merge-${{ github.event.number }} + VERSION=merge-${{ steps.generate_tag.outputs.GIT_TAG }} COMMIT=${{ github.sha }} BUILD_DATE=${{ steps.prep.outputs.created }} BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }} From 52ac7e307d36603fc1c85ca10df7b2b3616766b5 Mon Sep 17 00:00:00 2001 From: Safwan Date: Mon, 21 Jul 2025 13:30:00 +0500 Subject: [PATCH 13/19] skip e2e test --- .github/workflows/pull_request.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index a2447f67..8f5c3330 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -90,6 +90,12 @@ jobs: cd deployments/kubernetes/chart/reloader helm lint + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + username: ${{ secrets.STAKATER_DOCKERHUB_USERNAME }} + password: ${{ secrets.STAKATER_DOCKERHUB_PASSWORD }} + - name: Install kubectl run: | curl -LO "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl" @@ -110,6 +116,7 @@ jobs: - name: Test + if: false run: make test - name: Generate Tags From e0150145ecfb2c1602c8d7ac838a49c61eeff99f Mon Sep 17 00:00:00 2001 From: Safwan Date: Mon, 21 Jul 2025 13:50:21 +0500 Subject: [PATCH 14/19] updated PR workflow --- .github/workflows/pull_request.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 8f5c3330..a25ba937 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -90,11 +90,13 @@ jobs: cd deployments/kubernetes/chart/reloader helm lint - - name: Login to Docker Registry + - name: Login to ghcr registry uses: docker/login-action@v3 with: - username: ${{ secrets.STAKATER_DOCKERHUB_USERNAME }} - password: ${{ secrets.STAKATER_DOCKERHUB_PASSWORD }} + registry: ${{env.REGISTRY}} + username: stakater-user + password: ${{secrets.GITHUB_TOKEN}} + - name: Install kubectl run: | @@ -149,7 +151,7 @@ jobs: push: true build-args: | VERSION=merge-${{ steps.generate_tag.outputs.GIT_TAG }} - COMMIT=${{ github.sha }} + COMMIT=${{github.event.pull_request.head.sha}} BUILD_DATE=${{ steps.prep.outputs.created }} BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }} From d2580930e4177c1434c8ca3ba877d358e9ce76f2 Mon Sep 17 00:00:00 2001 From: Safwan Date: Mon, 21 Jul 2025 14:25:02 +0500 Subject: [PATCH 15/19] reverted testing changes --- .github/workflows/pull_request.yaml | 11 +---------- .../kubernetes/chart/reloader/templates/role.yaml | 2 -- deployments/kubernetes/chart/reloader/values.yaml | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index a25ba937..18381446 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -90,14 +90,6 @@ jobs: cd deployments/kubernetes/chart/reloader helm lint - - name: Login to ghcr registry - uses: docker/login-action@v3 - with: - registry: ${{env.REGISTRY}} - username: stakater-user - password: ${{secrets.GITHUB_TOKEN}} - - - name: Install kubectl run: | curl -LO "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl" @@ -118,7 +110,6 @@ jobs: - name: Test - if: false run: make test - name: Generate Tags @@ -148,7 +139,7 @@ jobs: context: . file: ${{ env.DOCKER_FILE_PATH }} pull: true - push: true + push: false build-args: | VERSION=merge-${{ steps.generate_tag.outputs.GIT_TAG }} COMMIT=${{github.event.pull_request.head.sha}} diff --git a/deployments/kubernetes/chart/reloader/templates/role.yaml b/deployments/kubernetes/chart/reloader/templates/role.yaml index dfb4c2c5..f0afe80b 100644 --- a/deployments/kubernetes/chart/reloader/templates/role.yaml +++ b/deployments/kubernetes/chart/reloader/templates/role.yaml @@ -32,8 +32,6 @@ rules: - list - get - watch - - create - - delete {{- if and (.Capabilities.APIVersions.Has "apps.openshift.io/v1") (.Values.reloader.isOpenshift) }} - apiGroups: - "apps.openshift.io" diff --git a/deployments/kubernetes/chart/reloader/values.yaml b/deployments/kubernetes/chart/reloader/values.yaml index a6e79d16..21c11a3e 100644 --- a/deployments/kubernetes/chart/reloader/values.yaml +++ b/deployments/kubernetes/chart/reloader/values.yaml @@ -17,7 +17,7 @@ fullnameOverride: "" image: name: stakater/reloader repository: ghcr.io/stakater/reloader - tag: test-image + tag: v1.4.5 # digest: sha256:1234567 pullPolicy: IfNotPresent From dcf4b0d0f64ed10c373b62f25b01a822848ebf50 Mon Sep 17 00:00:00 2001 From: Safwan Date: Mon, 21 Jul 2025 15:14:30 +0500 Subject: [PATCH 16/19] merged deployment pause related changes --- README.md | 21 +++++++++++++++++++++ internal/pkg/util/util.go | 2 ++ pkg/metainfo/metainfo.go | 7 +++++++ 3 files changed, 30 insertions(+) diff --git a/README.md b/README.md index 46b2c49d..f20b648f 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,25 @@ reloader: ALERT_ADDITIONAL_INFO: "Triggered by Reloader in staging environment" ``` +### 7. ⏸️ Pause Deployments + +This feature allows you to pause rollouts for a deployment for a specified duration, helping to prevent multiple restarts when several ConfigMaps or Secrets are updated in quick succession. + +| Annotation | Applies To | Description | +|---------------------------------------------------------|--------------|-----------------------------------------------------------------------------| +| `deployment.reloader.stakater.com/pause-period: "5m"` | Deployment | Pauses reloads for the specified period (e.g., `5m`, `1h`) | + +#### How it works + +1. Add the `deployment.reloader.stakater.com/pause-period` annotation to your Deployment, specifying the pause duration (e.g., `"5m"` for five minutes). +1. When a watched ConfigMap or Secret changes, Reloader will still trigger a reload event, but if the deployment is paused, the rollout will have no effect until the pause period has elapsed. +1. This avoids repeated restarts if multiple resources are updated close together. + +#### Use when + +1. βœ… Your deployment references multiple ConfigMaps or Secrets that may be updated at the same time. +1. βœ… You want to minimize unnecessary rollouts and reduce downtime caused by back-to-back configuration changes. + ## πŸš€ Installation ### 1. πŸ“¦ Helm @@ -336,6 +355,8 @@ These flags allow you to redefine annotation keys used in your workloads or reso | `--search-match-annotation` | Overrides `reloader.stakater.com/match` | | `--secret-annotation` | Overrides `secret.reloader.stakater.com/reload` | | `--configmap-annotation` | Overrides `configmap.reloader.stakater.com/reload` | +| `--pause-deployment-annotation` | Overrides `deployment.reloader.stakater.com/pause-period` | +| `--pause-deployment-time-annotation` | Overrides `deployment.reloader.stakater.com/paused-at` | ## Compatibility diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index 74455faf..f8a5ddaa 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -119,6 +119,8 @@ func ConfigureReloaderFlags(cmd *cobra.Command) { 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.PauseDeploymentAnnotation, "pause-deployment-annotation", "deployment.reloader.stakater.com/pause-period", "annotation to define the time period to pause a deployment after a configmap/secret change has been detected") + cmd.PersistentFlags().StringVar(&options.PauseDeploymentTimeAnnotation, "pause-deployment-time-annotation", "deployment.reloader.stakater.com/paused-at", "annotation to indicate when a deployment was paused by Reloader") 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") diff --git a/pkg/metainfo/metainfo.go b/pkg/metainfo/metainfo.go index 54b0be81..b4239c6f 100644 --- a/pkg/metainfo/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -55,6 +55,11 @@ type ReloaderOptions struct { SearchMatchAnnotation string `json:"searchMatchAnnotation"` // RolloutStrategyAnnotation is the annotation key used to define the rollout update strategy for workloads RolloutStrategyAnnotation string `json:"rolloutStrategyAnnotation"` + // PauseDeploymentAnnotation is the annotation key used to define the time period to pause a deployment after + PauseDeploymentAnnotation string `json:"pauseDeploymentAnnotation"` + // PauseDeploymentTimeAnnotation is the annotation key used to indicate when a deployment was paused by Reloader + PauseDeploymentTimeAnnotation string `json:"pauseDeploymentTimeAnnotation"` + // LogFormat specifies the log format to use (json, or empty string for default text format) LogFormat string `json:"logFormat"` // LogLevel specifies the log level to use (trace, debug, info, warning, error, fatal, panic) @@ -108,6 +113,8 @@ func GetReloaderOptions() *ReloaderOptions { AutoSearchAnnotation: options.AutoSearchAnnotation, SearchMatchAnnotation: options.SearchMatchAnnotation, RolloutStrategyAnnotation: options.RolloutStrategyAnnotation, + PauseDeploymentAnnotation: options.PauseDeploymentAnnotation, + PauseDeploymentTimeAnnotation: options.PauseDeploymentTimeAnnotation, LogFormat: options.LogFormat, LogLevel: options.LogLevel, IsArgoRollouts: parseBool(options.IsArgoRollouts), From d9b36a56b51ee36dd592d9095e4c077f31a2edd2 Mon Sep 17 00:00:00 2001 From: Safwan Date: Mon, 21 Jul 2025 17:40:50 +0500 Subject: [PATCH 17/19] Removed is dirty flag --- Dockerfile | 5 ++--- pkg/metainfo/metainfo.go | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 75d292cb..04c5e824 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,6 @@ ARG GOPRIVATE ARG COMMIT ARG VERSION ARG BUILD_DATE -ARG ISDIRTY WORKDIR /workspace @@ -38,8 +37,8 @@ RUN CGO_ENABLED=0 \ GO111MODULE=on \ go build -ldflags="-s -w -X github.com/stakater/Reloader/pkg/metainfo.Version=${VERSION} \ -X github.com/stakater/Reloader/pkg/metainfo.Commit=${COMMIT} \ - -X github.com/stakater/Reloader/pkg/metainfo.BuildDate=${BUILD_DATE} \ - -X github.com/stakater/Reloader/pkg/metainfo.IsDirty=${ISDIRTY}" -installsuffix 'static' -mod=mod -a -o manager ./ + -X github.com/stakater/Reloader/pkg/metainfo.BuildDate=${BUILD_DATE}" \ + -installsuffix 'static' -mod=mod -a -o manager ./ # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/pkg/metainfo/metainfo.go b/pkg/metainfo/metainfo.go index b4239c6f..8cfad3cb 100644 --- a/pkg/metainfo/metainfo.go +++ b/pkg/metainfo/metainfo.go @@ -12,7 +12,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Version, Commit, BuildDate, and IsDirty are set during the build process +// Version, Commit, and BuildDate are set during the build process // using the -X linker flag to inject these values into the binary. // They provide metadata about the build version, commit hash, build date, and whether there are // uncommitted changes in the source code at the time of build. @@ -20,7 +20,6 @@ import ( var Version = "dev" var Commit = "unknown" var BuildDate = "unknown" -var IsDirty = "false" const ( MetaInfoConfigmapName = "reloader-meta-info" @@ -140,8 +139,6 @@ type BuildInfo struct { ReleaseVersion string `json:"releaseVersion"` // CommitHash is the Git commit hash of the source code used to build this binary CommitHash string `json:"commitHash"` - // IsDirty indicates whether the working directory had uncommitted changes when built - IsDirty bool `json:"isDirty"` // CommitTime is the timestamp of the Git commit used to build this binary CommitTime time.Time `json:"commitTime"` } @@ -151,7 +148,6 @@ func NewBuildInfo() *BuildInfo { GoVersion: runtime.Version(), ReleaseVersion: Version, CommitHash: Commit, - IsDirty: parseBool(IsDirty), CommitTime: ParseUTCTime(BuildDate), } From 7af0728990f266a2282dad5672fdb3c67679b8fe Mon Sep 17 00:00:00 2001 From: Safwan Date: Tue, 22 Jul 2025 13:47:50 +0500 Subject: [PATCH 18/19] comments resolved --- .github/workflows/pull_request.yaml | 1 + Dockerfile | 1 - .../chart/reloader/templates/role.yaml | 4 -- .../chart/reloader/templates/rolebinding.yaml | 4 -- deployments/kubernetes/kustomization.yaml | 1 + .../kubernetes/manifests/deployment.yaml | 7 ++ deployments/kubernetes/manifests/role.yaml | 32 +++++++++ deployments/kubernetes/reloader.yaml | 68 +++++++++---------- 8 files changed, 74 insertions(+), 44 deletions(-) create mode 100644 deployments/kubernetes/manifests/role.yaml diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 18381446..de4b6240 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -62,6 +62,7 @@ jobs: go-version-file: 'go.mod' check-latest: true cache: true + - name: Create timestamp id: prep run: echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT diff --git a/Dockerfile b/Dockerfile index 04c5e824..34c8941e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,6 @@ ARG COMMIT ARG VERSION ARG BUILD_DATE - WORKDIR /workspace # Copy the Go Modules manifests diff --git a/deployments/kubernetes/chart/reloader/templates/role.yaml b/deployments/kubernetes/chart/reloader/templates/role.yaml index f0afe80b..70a68157 100644 --- a/deployments/kubernetes/chart/reloader/templates/role.yaml +++ b/deployments/kubernetes/chart/reloader/templates/role.yaml @@ -105,11 +105,7 @@ rules: --- {{- if .Values.reloader.rbac.enabled }} -{{- if (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }} apiVersion: rbac.authorization.k8s.io/v1 -{{ else }} -apiVersion: rbac.authorization.k8s.io/v1beta1 -{{- end }} kind: Role metadata: annotations: diff --git a/deployments/kubernetes/chart/reloader/templates/rolebinding.yaml b/deployments/kubernetes/chart/reloader/templates/rolebinding.yaml index b1c2b05b..5cf4cf38 100644 --- a/deployments/kubernetes/chart/reloader/templates/rolebinding.yaml +++ b/deployments/kubernetes/chart/reloader/templates/rolebinding.yaml @@ -30,11 +30,7 @@ subjects: --- {{- if .Values.reloader.rbac.enabled }} -{{- if (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }} apiVersion: rbac.authorization.k8s.io/v1 -{{ else }} -apiVersion: rbac.authorization.k8s.io/v1beta1 -{{- end }} kind: RoleBinding metadata: annotations: diff --git a/deployments/kubernetes/kustomization.yaml b/deployments/kubernetes/kustomization.yaml index b4b7efbf..9bc6038a 100644 --- a/deployments/kubernetes/kustomization.yaml +++ b/deployments/kubernetes/kustomization.yaml @@ -6,3 +6,4 @@ resources: - manifests/clusterrolebinding.yaml - manifests/serviceaccount.yaml - manifests/deployment.yaml + - manifests/role.yaml diff --git a/deployments/kubernetes/manifests/deployment.yaml b/deployments/kubernetes/manifests/deployment.yaml index fb80fa47..11aa749d 100644 --- a/deployments/kubernetes/manifests/deployment.yaml +++ b/deployments/kubernetes/manifests/deployment.yaml @@ -31,6 +31,13 @@ spec: resourceFieldRef: resource: limits.memory divisor: '1' + - name: RELOADER_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + + - name: RELOADER_DEPLOYMENT_NAME + value: reloader-reloader ports: - name: http containerPort: 9090 diff --git a/deployments/kubernetes/manifests/role.yaml b/deployments/kubernetes/manifests/role.yaml new file mode 100644 index 00000000..0224ab9e --- /dev/null +++ b/deployments/kubernetes/manifests/role.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: reloader-reloader-metadata-role + namespace: default +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - list + - get + - watch + - create + - update + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: reloader-reloader-metadata-rolebinding + namespace: default +subjects: + - kind: ServiceAccount + name: reloader-reloader + namespace: default +roleRef: + kind: Role + name: reloader-reloader-metadata-role + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/deployments/kubernetes/reloader.yaml b/deployments/kubernetes/reloader.yaml index 1222d56c..2abbdd19 100644 --- a/deployments/kubernetes/reloader.yaml +++ b/deployments/kubernetes/reloader.yaml @@ -5,6 +5,23 @@ metadata: namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: reloader-reloader-metadata-role + namespace: default +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - list + - get + - watch + - create + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: reloader-reloader-role @@ -64,6 +81,20 @@ rules: - patch --- apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: reloader-reloader-metadata-rolebinding + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: reloader-reloader-metadata-role +subjects: +- kind: ServiceAccount + name: reloader-reloader + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: reloader-reloader-role-binding @@ -76,37 +107,6 @@ subjects: name: reloader-reloader namespace: default --- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: reloader-metadata-role - namespace: default -rules: - - apiGroups: - - "" - resources: - - configmaps - verbs: - - list - - get - - watch - - create - - update ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: reloader-metadata-role-binding - namespace: default -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: reloader-metadata-role -subjects: - - kind: ServiceAccount - name: reloader-reloader - namespace: default ---- apiVersion: apps/v1 kind: Deployment metadata: @@ -139,11 +139,9 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - - name: RELOADER_DEPLOYMENT_NAME - value: default - - image: "ghcr.io/stakater/reloader:latest" + value: reloader-reloader + image: ghcr.io/stakater/reloader:test-image imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 5 From 82eb8d8b87b3db72a011c83afbdd9cda3c7b756d Mon Sep 17 00:00:00 2001 From: Safwan Date: Tue, 22 Jul 2025 14:40:46 +0500 Subject: [PATCH 19/19] reverted reloader.yaml deployment name --- deployments/kubernetes/reloader.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/kubernetes/reloader.yaml b/deployments/kubernetes/reloader.yaml index 2abbdd19..9151e64d 100644 --- a/deployments/kubernetes/reloader.yaml +++ b/deployments/kubernetes/reloader.yaml @@ -141,7 +141,7 @@ spec: fieldPath: metadata.namespace - name: RELOADER_DEPLOYMENT_NAME value: reloader-reloader - image: ghcr.io/stakater/reloader:test-image + image: ghcr.io/stakater/reloader:latest imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 5