143 lines
3.7 KiB
Go
143 lines
3.7 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright Authors of K9s
|
|
|
|
package dao
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"strings"
|
|
|
|
"github.com/derailed/k9s/internal/slogs"
|
|
v1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/cli-runtime/pkg/printers"
|
|
)
|
|
|
|
// Secret represents a secret K8s resource.
|
|
type Secret struct {
|
|
Resource
|
|
decodeData bool
|
|
}
|
|
|
|
// Describe describes a secret that can be encoded or decoded.
|
|
func (s *Secret) Describe(path string) (string, error) {
|
|
encodedDescription, err := s.Generic.Describe(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if s.decodeData {
|
|
return s.Decode(encodedDescription, path)
|
|
}
|
|
|
|
return encodedDescription, nil
|
|
}
|
|
|
|
// ToYAML returns a resource yaml.
|
|
func (s *Secret) ToYAML(path string, showManaged bool) (string, error) {
|
|
if s.decodeData {
|
|
return s.decodeYAML(path, showManaged)
|
|
}
|
|
|
|
return s.Generic.ToYAML(path, showManaged)
|
|
}
|
|
|
|
func (s *Secret) decodeYAML(path string, showManaged bool) (string, error) {
|
|
o, err := s.Get(context.Background(), path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
o = o.DeepCopyObject()
|
|
u, ok := o.(*unstructured.Unstructured)
|
|
if !ok {
|
|
return "", fmt.Errorf("expecting unstructured but got %T", o)
|
|
}
|
|
if u.Object == nil {
|
|
return "", fmt.Errorf("expecting unstructured object but got nil")
|
|
}
|
|
if !showManaged {
|
|
if meta, ok := u.Object["metadata"].(map[string]any); ok {
|
|
delete(meta, "managedFields")
|
|
}
|
|
}
|
|
if decoded, err := ExtractSecrets(o); err == nil {
|
|
u.Object["data"] = decoded
|
|
}
|
|
|
|
var (
|
|
buff bytes.Buffer
|
|
p printers.YAMLPrinter
|
|
)
|
|
if err := p.PrintObj(o, &buff); err != nil {
|
|
slog.Error("PrintObj failed", slogs.Error, err)
|
|
return "", err
|
|
}
|
|
|
|
return buff.String(), nil
|
|
}
|
|
|
|
// SetDecodeData toggles decode mode.
|
|
func (s *Secret) SetDecodeData(b bool) {
|
|
s.decodeData = b
|
|
}
|
|
|
|
// Decode removes the encoded part from the secret's description and appends the
|
|
// secret's decoded data.
|
|
func (s *Secret) Decode(encodedDescription, path string) (string, error) {
|
|
dataEndIndex := strings.Index(encodedDescription, "====")
|
|
if dataEndIndex == -1 {
|
|
return "", fmt.Errorf("unable to find data section in secret description")
|
|
}
|
|
|
|
dataEndIndex += 4
|
|
if dataEndIndex >= len(encodedDescription) {
|
|
return "", fmt.Errorf("data section in secret description is invalid")
|
|
}
|
|
|
|
// Remove the encoded part from k8s's describe API
|
|
// More details about the reasoning of index: https://github.com/kubernetes/kubectl/blob/v0.29.0/pkg/describe/describe.go#L2542
|
|
body := encodedDescription[0:dataEndIndex]
|
|
|
|
o, err := s.Get(context.Background(), path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
data, err := ExtractSecrets(o)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
decodedSecrets := make([]string, 0, len(data))
|
|
for k, v := range data {
|
|
line := fmt.Sprintf("%s: %s", k, v)
|
|
decodedSecrets = append(decodedSecrets, strings.TrimSpace(line))
|
|
}
|
|
|
|
return body + "\n" + strings.Join(decodedSecrets, "\n"), nil
|
|
}
|
|
|
|
// ExtractSecrets takes an unstructured object and attempts to convert it into a
|
|
// Kubernetes Secret.
|
|
// It returns a map where the keys are the secret data keys and the values are
|
|
// the corresponding secret data values.
|
|
// If the conversion fails, it returns an error.
|
|
func ExtractSecrets(o runtime.Object) (map[string]string, error) {
|
|
u, ok := o.(*unstructured.Unstructured)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expecting *unstructured.Unstructured but got %T", o)
|
|
}
|
|
var secret v1.Secret
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &secret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
secretData := make(map[string]string, len(secret.Data))
|
|
for k, val := range secret.Data {
|
|
secretData[k] = string(val)
|
|
}
|
|
|
|
return secretData, nil
|
|
}
|