diff --git a/Dockerfile b/Dockerfile index 57a3999..41d0238 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 21fc756..9906e25 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 287567b..0c6ff88 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 a031e3e..3ee14ae 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 254420b..6880bc3 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 f0aac83..3a3d83c 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 7c0e14e..260b052 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 0000000..b4e33f0 --- /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 1a2696d..fdec73b 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 +}