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
154 changes: 153 additions & 1 deletion api/filters/replacement/replacement.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package replacement

import (
"encoding/json"
"fmt"
"strings"

Expand Down Expand Up @@ -188,6 +189,14 @@ func copyValueToTarget(target *yaml.RNode, value *yaml.RNode, selector *types.Ta
if selector.Options != nil && selector.Options.Create {
createKind = value.YNode().Kind
}

// Check if this fieldPath contains structured data access
if err := setValueInStructuredData(target, value, fp, createKind); err == nil {
// Successfully handled as structured data
continue
}

// Fall back to normal path handling
targetFieldList, err := target.Pipe(&yaml.PathMatcher{
Path: kyaml_utils.SmarterPathSplitter(fp, "."),
Create: createKind})
Expand All @@ -204,7 +213,7 @@ func copyValueToTarget(target *yaml.RNode, value *yaml.RNode, selector *types.Ta

for _, t := range targetFields {
if err := setFieldValue(selector.Options, t, value); err != nil {
return err
return fmt.Errorf("%w", err)
}
}
}
Expand Down Expand Up @@ -247,3 +256,146 @@ func setFieldValue(options *types.FieldOptions, targetField *yaml.RNode, value *

return nil
}

// setValueInStructuredData handles setting values within structured data (JSON/YAML) in scalar fields
func setValueInStructuredData(target *yaml.RNode, value *yaml.RNode, fieldPath string, createKind yaml.Kind) error {
pathParts := kyaml_utils.SmarterPathSplitter(fieldPath, ".")
if len(pathParts) < 2 {
return fmt.Errorf("not a structured data path")
}

// Find the potential scalar field that might contain structured data
var scalarFieldPath []string
var structuredDataPath []string
var foundScalar = false

// Try to find where the scalar field ends and structured data begins
for i := 1; i <= len(pathParts); i++ {
potentialScalarPath := pathParts[:i]
scalarField, err := target.Pipe(yaml.Lookup(potentialScalarPath...))
if err != nil {
continue
}
if scalarField != nil && scalarField.YNode().Kind == yaml.ScalarNode && i < len(pathParts) {
// Try to parse the scalar value as structured data
scalarValue := scalarField.YNode().Value
var parsedNode yaml.Node
if err := yaml.Unmarshal([]byte(scalarValue), &parsedNode); err == nil {
// Successfully parsed - this is structured data
scalarFieldPath = potentialScalarPath
structuredDataPath = pathParts[i:]
foundScalar = true
break
}
}
}

if !foundScalar {
return fmt.Errorf("no structured data found in path")
}

// Get the scalar field containing structured data
scalarField, err := target.Pipe(yaml.Lookup(scalarFieldPath...))
if err != nil {
return fmt.Errorf("%w", err)
}

// Parse the structured data
scalarValue := scalarField.YNode().Value
var parsedNode yaml.Node
if err := yaml.Unmarshal([]byte(scalarValue), &parsedNode); err != nil {
return fmt.Errorf("%w", err)
}

structuredData := yaml.NewRNode(&parsedNode)

// Navigate to the target location within the structured data
targetInStructured, err := structuredData.Pipe(&yaml.PathMatcher{
Path: structuredDataPath,
Create: createKind,
})
if err != nil {
return fmt.Errorf("%w", err)
}

targetFields, err := targetInStructured.Elements()
if err != nil {
return fmt.Errorf("%w", err)
}

if len(targetFields) == 0 {
return fmt.Errorf("unable to find field in structured data")
}

// Set the value in the structured data
for _, t := range targetFields {
if t.YNode().Kind == yaml.ScalarNode {
t.YNode().Value = value.YNode().Value
} else {
t.SetYNode(value.YNode())
}
}

// Serialize the modified structured data back to the scalar field
// Try to detect if original was JSON or YAML and preserve formatting
serializedData, err := serializeStructuredData(structuredData, scalarValue)
if err != nil {
return fmt.Errorf("%w", err)
}

// Update the original scalar field
scalarField.YNode().Value = serializedData

return nil
}

// serializeStructuredData handles the serialization of structured data back to string format
// preserving the original format (JSON vs YAML) and style (pretty vs compact)
func serializeStructuredData(structuredData *yaml.RNode, originalValue string) (string, error) {
firstChar := rune(strings.TrimSpace(originalValue)[0])
if firstChar == '{' || firstChar == '[' {
return serializeAsJSON(structuredData, originalValue)
}

// Fallback to YAML format
return serializeAsYAML(structuredData)
}

// serializeAsJSON converts structured data back to JSON format
func serializeAsJSON(structuredData *yaml.RNode, originalValue string) (string, error) {
modifiedData, err := structuredData.String()
if err != nil {
return "", fmt.Errorf("failed to serialize structured data: %w", err)
}

// Parse the YAML output as JSON
var jsonData interface{}
if err := yaml.Unmarshal([]byte(modifiedData), &jsonData); err != nil {
return "", fmt.Errorf("failed to unmarshal YAML data: %w", err)
}

// Check if original was pretty-printed by looking for newlines and indentation
if strings.Contains(originalValue, "\n") && strings.Contains(originalValue, " ") {
// Pretty-print the JSON to match original formatting
if prettyJSON, err := json.MarshalIndent(jsonData, "", " "); err == nil {
return string(prettyJSON), nil
}
}

// Compact JSON
if compactJSON, err := json.Marshal(jsonData); err == nil {
return string(compactJSON), nil
}

return "", fmt.Errorf("failed to marshal JSON data")
}

// serializeAsYAML converts structured data back to YAML format
func serializeAsYAML(structuredData *yaml.RNode) (string, error) {
modifiedData, err := structuredData.String()
if err != nil {
return "", fmt.Errorf("failed to serialize YAML data: %w", err)
}

return strings.TrimSpace(modifiedData), nil
}
Loading
Loading