Files
troubleshoot/pkg/collect/configmap.go
ada mancini 03c53cabd6 make ConfigMap collector namespace-optional (#1212)
* try to load default namespace from kubectl current context

* use  ns from kubectl context when no ns is given in the spec

* remove test prints

* Update pkg/collect/configmap.go

Co-authored-by: Diamon Wiggins <38189728+diamonwiggins@users.noreply.github.com>

---------

Co-authored-by: Diamon Wiggins <38189728+diamonwiggins@users.noreply.github.com>
2023-06-13 13:12:17 -04:00

173 lines
5.6 KiB
Go

package collect
import (
"bytes"
"context"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
)
type ConfigMapOutput struct {
Namespace string `json:"namespace"`
Name string `json:"name"`
Key string `json:"key"`
ConfigMapExists bool `json:"configMapExists"`
KeyExists bool `json:"keyExists"`
Value string `json:"value,omitempty"`
Data map[string]string `json:"data,omitonempty"`
}
type CollectConfigMap struct {
Collector *troubleshootv1beta2.ConfigMap
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectConfigMap) Title() string {
return getCollectorName(c)
}
func (c *CollectConfigMap) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectConfigMap) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
output := NewResult()
configMaps := []corev1.ConfigMap{}
namespace := c.Collector.Namespace
if c.Collector.Name != "" {
if namespace == "" {
kubeconfig := k8sutil.GetKubeconfig()
ns, _, err := kubeconfig.Namespace()
klog.V(2).Infof("no namespace was set for configmap '%s': using namespace '%s' from current kubeconfig context", c.Collector.Name, ns)
if err != nil {
return nil, errors.Wrapf(err, "a namespace was not specified for configmap '%s' and could not be discovered from kubeconfig", c.Collector.Name)
}
namespace = ns
}
klog.V(1).Infof("looking for configmap '%s' in namespace '%s'", c.Collector.Name, namespace)
configMap, err := c.Client.CoreV1().ConfigMaps(namespace).Get(c.Context, c.Collector.Name, metav1.GetOptions{})
if err != nil {
if kuberneteserrors.IsNotFound(err) {
filePath, encoded, err := configMapToOutput(c.Collector, nil, c.Collector.Name)
if err != nil {
return output, errors.Wrapf(err, "collect secret %s", c.Collector.Name)
}
output.SaveResult(c.BundlePath, filePath, bytes.NewBuffer(encoded))
}
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
return output, nil
}
configMaps = append(configMaps, *configMap)
} else if len(c.Collector.Selector) > 0 {
cms, err := listConfigMapsForSelector(c.Context, c.Client, c.Collector.Namespace, c.Collector.Selector)
if err != nil {
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
return output, nil
}
configMaps = append(configMaps, cms...)
} else {
return nil, errors.New("either name or selector must be specified")
}
for _, configMap := range configMaps {
filePath, encoded, err := configMapToOutput(c.Collector, &configMap, configMap.Name)
if err != nil {
return output, errors.Wrapf(err, "collect configMap %s", configMap.Name)
}
output.SaveResult(c.BundlePath, filePath, bytes.NewBuffer(encoded))
}
return output, nil
}
func configMapToOutput(configMapCollector *troubleshootv1beta2.ConfigMap, configMap *corev1.ConfigMap, configMapName string) (string, []byte, error) {
foundConfigMap := ConfigMapOutput{
Namespace: configMapCollector.Namespace,
Name: configMapName,
Key: configMapCollector.Key,
}
if configMap != nil {
foundConfigMap.ConfigMapExists = true
if configMapCollector.IncludeAllData {
foundConfigMap.Data = configMap.Data
}
if configMapCollector.Key != "" {
if val, ok := configMap.Data[configMapCollector.Key]; ok {
foundConfigMap.KeyExists = true
if configMapCollector.IncludeValue {
foundConfigMap.Value = string(val)
}
}
}
}
return marshalConfigMapOutput(configMapCollector, foundConfigMap)
}
func listConfigMapsForSelector(ctx context.Context, client kubernetes.Interface, namespace string, selector []string) ([]corev1.ConfigMap, error) {
serializedLabelSelector := strings.Join(selector, ",")
listOptions := metav1.ListOptions{
LabelSelector: serializedLabelSelector,
}
configMaps, err := client.CoreV1().ConfigMaps(namespace).List(ctx, listOptions)
if err != nil {
return nil, err
}
return configMaps.Items, nil
}
func marshalConfigMapOutput(configMapCollector *troubleshootv1beta2.ConfigMap, configMap ConfigMapOutput) (string, []byte, error) {
path := GetConfigMapFileName(configMapCollector, configMap.Name)
b, err := json.MarshalIndent(configMap, "", " ")
if err != nil {
return path, nil, err
}
return path, b, nil
}
func GetConfigMapFileName(configMapCollector *troubleshootv1beta2.ConfigMap, name string) string {
parts := []string{"configmaps", configMapCollector.Namespace, name}
if configMapCollector.Key != "" {
parts = append(parts, configMapCollector.Key)
}
return fmt.Sprintf("%s.json", filepath.Join(parts...))
}
func GetConfigMapErrorsFileName(configMapCollector *troubleshootv1beta2.ConfigMap) string {
parts := []string{"configmaps-errors", configMapCollector.Namespace}
if configMapCollector.Name != "" {
parts = append(parts, configMapCollector.Name)
} else {
parts = append(parts, selectorToString(configMapCollector.Selector))
}
if configMapCollector.Key != "" {
parts = append(parts, configMapCollector.Key)
}
return fmt.Sprintf("%s.json", filepath.Join(parts...))
}