Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 60 additions & 58 deletions core/commoncmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ import (
"github.com/opensvc/om3/v3/util/key"
)

type parsedLine struct {
raw string
isKV bool
key string
keyPrefix string
delim string
inlineComment string
}

type sectionData struct {
name string
lines []parsedLine
values map[string]string
}

func APIKeywordItemsToRaw(items api.KeywordItems) rawconfig.T {
r := rawconfig.T{}
r.Data = orderedmap.New()
Expand All @@ -34,10 +49,24 @@ func APIKeywordItemsToRaw(items api.KeywordItems) rawconfig.T {
}

var (
sectionRE = regexp.MustCompile(`^\s*\[.*\]\s*$`)
commentRE = regexp.MustCompile(`^\s*[#;]`)
sectionRE = regexp.MustCompile(`^\s*\[.*\]\s*$`)
commentRE = regexp.MustCompile(`^\s*[#;]`)
keyValueRE = regexp.MustCompile(`^(\s*[^=\s#;]+)(\s*=\s*)([^#;]*)(.*)$`)
)

func sectionName(s string) string {
s = strings.TrimSpace(s)
if !strings.HasPrefix(s, "[") {
return ""
}
if !strings.HasSuffix(s, "]") {
return ""
}
s = s[1 : len(s)-1]
s = strings.TrimSpace(s)
return s
}

func Sections(b []byte, sections []string) []byte {
if len(sections) == 0 {
return b
Expand All @@ -49,18 +78,6 @@ func Sections(b []byte, sections []string) []byte {
for _, section := range sections {
m[section] = nil
}
sectionName := func(s string) string {
s = strings.TrimSpace(s)
if !strings.HasPrefix(s, "[") {
return ""
}
if !strings.HasSuffix(s, "]") {
return ""
}
s = s[1 : len(s)-1]
s = strings.TrimSpace(s)
return s
}
isValidSection := func(s string) bool {
_, ok := m[s]
return ok
Expand Down Expand Up @@ -124,11 +141,12 @@ func ColorizeINI(b []byte) []byte {
// Key-value
if strings.Contains(line, "=") && !strings.HasPrefix(line, " ") && !strings.HasPrefix(line, "\t") {
// Use regex to preserve spacing around equals sign
kvRE := regexp.MustCompile(`^(\s*[^=\s]+(?:\s+[^=\s]+)*)\s*=\s*(.*)$`)
matches := kvRE.FindStringSubmatch(line)
if len(matches) == 3 {
matches := keyValueRE.FindStringSubmatch(line)
if len(matches) == 5 {
key := matches[1]
equalAndValue := matches[2]
delim := matches[2]
value := matches[3]
inlineComment := matches[4]

// Colorize key
key, scope, scopeFound := strings.Cut(key, "@")
Expand All @@ -137,49 +155,33 @@ func ColorizeINI(b []byte) []byte {
color.Set(color.FgHiMagenta).Fprint(out, "@"+scope)
}

// Find the equals sign position to preserve exact spacing
equalPos := strings.Index(line, "=")
if equalPos >= 0 {
// Extract the equals sign with surrounding spaces
start := equalPos
end := equalPos + 1
// Include leading spaces
for start > 0 && line[start-1] == ' ' {
start--
}
// Include trailing spaces
for end < len(line) && line[end] == ' ' {
end++
}
equalSign := line[start:end]
color.Set(color.FgHiBlack).Fprint(out, equalSign)

// The rest is the value
value := line[end:]

// Highlight references in the value
referenceMatches := referenceRE.FindAllStringIndex(value, -1)
if len(referenceMatches) > 0 {
lastPos := 0
for _, match := range referenceMatches {
// Write non-reference part
out.WriteString(value[lastPos:match[0]])

// Write reference part in green + bold
referenceText := value[match[0]:match[1]]
color.Set(color.FgGreen, color.Bold).Fprint(out, referenceText)
lastPos = match[1]
}
// Write remaining part after last reference
out.WriteString(value[lastPos:])
} else {
// No references
out.WriteString(value)
// Colorize delimiter
color.Set(color.FgHiBlack).Fprint(out, delim)

// Highlight references in the value
referenceMatches := referenceRE.FindAllStringIndex(value, -1)
if len(referenceMatches) > 0 {
lastPos := 0
for _, match := range referenceMatches {
// Write non-reference part
out.WriteString(value[lastPos:match[0]])

// Write reference part in green + bold
referenceText := value[match[0]:match[1]]
color.Set(color.FgGreen, color.Bold).Fprint(out, referenceText)
lastPos = match[1]
}
// Write remaining part after last reference
out.WriteString(value[lastPos:])
} else {
// Fallback: output the rest as-is
out.WriteString(equalAndValue)
// No references
out.WriteString(value)
}

if inlineComment != "" {
color.Set(color.FgHiBlack, color.Italic).Fprint(out, inlineComment)
}

out.WriteString("\n")

// Check if line continues
Expand Down
4 changes: 4 additions & 0 deletions core/commoncmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,3 +741,7 @@ func FlagSCSILUN(flags *pflag.FlagSet, p *string) {
func FlagIgnoreNoCollectorConfigured(flags *pflag.FlagSet, p *bool) {
flags.BoolVar(p, "ignore-no-collector-configured", false, "ignore absence of collector configuration")
}

func FlagRedactSecrets(flags *pflag.FlagSet, p *bool) {
flags.BoolVar(p, "redact-secrets", false, "hide secret values in the output")
}
3 changes: 3 additions & 0 deletions core/keywords/keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ type (

// Minimal force the keyword to be included in the minimal configlet of the driver doc.
Minimal bool

// RedactSecret means the keyword value will be hidden on config show with the flag --redact-secrets.
RedactSecret bool
}

Store []*Keyword
Expand Down
18 changes: 10 additions & 8 deletions core/object/node_keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ var (
Text: keywords.NewText(fs, "text/kw/node/node.oci"),
}
kwNodeUUID = keywords.Keyword{
Option: "uuid",
Section: "node",
Text: keywords.NewText(fs, "text/kw/node/node.uuid"),
Option: "uuid",
Section: "node",
Text: keywords.NewText(fs, "text/kw/node/node.uuid"),
RedactSecret: true,
}
kwNodePRKey = keywords.Keyword{
DefaultText: keywords.NewText(fs, "text/kw/node/node.prkey.default"),
Expand Down Expand Up @@ -652,11 +653,12 @@ var (
Text: keywords.NewText(fs, "text/kw/node/cluster.name"),
}
kwNodeClusterSecret = keywords.Keyword{
DefaultText: keywords.NewText(fs, "text/kw/node/cluster.secret.default"),
Option: "secret",
Scopable: true,
Section: "cluster",
Text: keywords.NewText(fs, "text/kw/node/cluster.secret"),
DefaultText: keywords.NewText(fs, "text/kw/node/cluster.secret.default"),
Option: "secret",
Scopable: true,
Section: "cluster",
Text: keywords.NewText(fs, "text/kw/node/cluster.secret"),
RedactSecret: true,
}
kwNodeClusterNodes = keywords.Keyword{
Converter: "list",
Expand Down
97 changes: 97 additions & 0 deletions core/object/redact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package object

import (
"bufio"
"bytes"
"regexp"
"strings"

"github.com/opensvc/om3/v3/core/keywords"
"github.com/opensvc/om3/v3/core/naming"
"github.com/opensvc/om3/v3/util/key"
"github.com/opensvc/om3/v3/util/redact"
)

var (
sectionRE = regexp.MustCompile(`^\s*\[.*\]\s*$`)
commentRE = regexp.MustCompile(`^\s*[#;]`)
keyValueRE = regexp.MustCompile(`^(\s*[^=\s#;]+)(\s*=\s*)([^#;]*)(.*)$`)
)

func sectionName(s string) string {
s = strings.TrimSpace(s)
if !strings.HasPrefix(s, "[") {
return ""
}
if !strings.HasSuffix(s, "]") {
return ""
}
s = s[1 : len(s)-1]
s = strings.TrimSpace(s)
return s
}

func RedactSecrets(b []byte, kind string) []byte {
var (
sections []redact.SectionData
current redact.SectionData
secrets map[redact.KeywordItem]bool
)
secrets = make(map[redact.KeywordItem]bool)
scanner := bufio.NewScanner(bytes.NewReader(b))
for scanner.Scan() {
line := scanner.Text()
if sectionRE.MatchString(line) {
if current.Name != "" && len(current.Lines) > 0 {
sections = append(sections, current)
}
current = redact.SectionData{
Name: sectionName(line),
Values: make(map[string]string),
}
current.Lines = append(current.Lines, redact.ParsedLine{Raw: line})
continue
}

matches := keyValueRE.FindStringSubmatch(line)
if len(matches) == 5 && !commentRE.MatchString(line) {
k := strings.TrimSpace(matches[1])
v := strings.TrimSpace(matches[3])

current.Values[k] = v
current.Lines = append(current.Lines, redact.ParsedLine{
Raw: line,
IsKV: true,
KeyPrefix: matches[1],
Delim: matches[2],
Key: k,
InlineComment: matches[4],
})
continue
}
current.Lines = append(current.Lines, redact.ParsedLine{Raw: line})
}
sections = append(sections, current)

k := naming.Kind(kind)
var store keywords.Store
if k == naming.KindInvalid {
store = NodeKeywordStore
} else {
store = KeywordStoreWithDrivers(k)
}

for _, section := range sections {
sectionType := section.Values["type"]
dataSec := (k == naming.KindSec || k == naming.KindUsr) && section.Name == "data"
for _, l := range section.Lines {
kw := store.Lookup(key.New(section.Name, l.Key), k, sectionType)
if dataSec || (kw != nil && kw.RedactSecret) {
secrets[redact.KeywordItem{SectionName: section.Name, Key: l.Key}] = true
continue
}
}
}

return redact.RedactSecrets(sections, secrets)
}
2 changes: 2 additions & 0 deletions core/om/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,7 @@ func newCmdNodeConfigShow() *cobra.Command {
flags := cmd.Flags()
commoncmd.FlagNodeSelector(flags, &options.NodeSelector)
commoncmd.FlagSections(flags, &options.Sections)
commoncmd.FlagRedactSecrets(flags, &options.RedactSecrets)
return cmd
}

Expand Down Expand Up @@ -2651,6 +2652,7 @@ func newCmdObjectConfigShow(kind string) *cobra.Command {
flags := cmd.Flags()
commoncmd.FlagObjectSelector(flags, &options.ObjectSelector)
commoncmd.FlagSections(flags, &options.Sections)
commoncmd.FlagRedactSecrets(flags, &options.RedactSecrets)
return cmd
}

Expand Down
17 changes: 12 additions & 5 deletions core/omcmd/lib_remote_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/opensvc/om3/v3/core/client"
"github.com/opensvc/om3/v3/core/naming"
"github.com/opensvc/om3/v3/core/rawconfig"
"github.com/opensvc/om3/v3/daemon/api"
)

func createTempRemoteConfig(p naming.Path, c *client.T) (string, error) {
Expand All @@ -17,7 +18,7 @@ func createTempRemoteConfig(p naming.Path, c *client.T) (string, error) {
buff []byte
f *os.File
)
if buff, err = fetchConfig(p, c); err != nil {
if buff, err = fetchConfig(p, c, false); err != nil {
return "", err
}
if f, err = os.CreateTemp(rawconfig.Paths.Tmp, "remote.*.conf.tmp"); err != nil {
Expand Down Expand Up @@ -57,8 +58,11 @@ func remoteClient(p naming.Path, c *client.T) (*client.T, error) {
return c, nil
}

func fetchNodeConfig(nodename string, c *client.T) ([]byte, error) {
resp, err := c.GetNodeConfigFileWithResponse(context.Background(), nodename)
func fetchNodeConfig(nodename string, c *client.T, redactSecrets bool) ([]byte, error) {
params := api.GetNodeConfigFileParams{
RedactSecrets: &redactSecrets,
}
resp, err := c.GetNodeConfigFileWithResponse(context.Background(), nodename, &params)
if err != nil {
return nil, err
} else if resp.StatusCode() != http.StatusOK {
Expand All @@ -67,8 +71,11 @@ func fetchNodeConfig(nodename string, c *client.T) ([]byte, error) {
return resp.Body, nil
}

func fetchConfig(p naming.Path, c *client.T) ([]byte, error) {
resp, err := c.GetObjectConfigFileWithResponse(context.Background(), p.Namespace, p.Kind, p.Name)
func fetchConfig(p naming.Path, c *client.T, redactSecrets bool) ([]byte, error) {
params := api.GetObjectConfigFileParams{
RedactSecrets: &redactSecrets,
}
resp, err := c.GetObjectConfigFileWithResponse(context.Background(), p.Namespace, p.Kind, p.Name, &params)
if err != nil {
return nil, err
} else if resp.StatusCode() != http.StatusOK {
Expand Down
Loading