Skip to content
Draft
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
1 change: 1 addition & 0 deletions go/fn/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const (

// UnknownNamespace is the special char for cluster-scoped or unknown-scoped resources. This is only used in upstream-identifier
UnknownNamespace = "~C"

// DefaultNamespace is the actual namespace value if a namespace-scoped resource has its namespace field unspecified.
DefaultNamespace = "default"
)
Expand Down
6 changes: 3 additions & 3 deletions go/fn/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ go 1.24.3
require (
github.com/go-errors/errors v1.5.1
github.com/google/go-cmp v0.7.0
github.com/kptdev/kpt v1.0.0-beta.57.0.20250625181933-26ae79c92ed2
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.10.0
gotest.tools v2.2.0+incompatible
k8s.io/apimachinery v0.33.1
// We must not include any core k8s APIs (e.g. k8s.io/api) in
// the dependencies, depending on them will likely to cause version skew for
// consumers. The dependencies for tests and examples should be isolated.
k8s.io/klog/v2 v2.130.1
sigs.k8s.io/kustomize/kyaml v0.19.0

)

require github.com/kptdev/kpt v1.0.0-beta.57.0.20250625181933-26ae79c92ed2

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/go-logr/logr v1.4.2 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go/fn/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -82,6 +84,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4=
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
Expand Down
102 changes: 46 additions & 56 deletions go/fn/internal/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,23 @@ type MapVariant struct {
node *yaml.Node
}

func (o *MapVariant) GetKind() variantKind {
return variantKindMap
func (o *MapVariant) GetKind() VariantKind {
return VariantKindMap
}

func (o *MapVariant) Node() *yaml.Node {
return o.node
}

func (o *MapVariant) IsEmpty() bool {
return o == nil || o.node == nil || len(o.node.Content) == 0
}

func (o *MapVariant) HasKey(key string) bool {
_, found := o.getVariant(key)
return found
}

func (o *MapVariant) Entries() (map[string]variant, error) {
entries := make(map[string]variant)

Expand Down Expand Up @@ -85,13 +94,6 @@ func (o *MapVariant) Entries() (map[string]variant, error) {
return entries, nil
}

func asString(node *yaml.Node) (string, bool) {
if node.Kind == yaml.ScalarNode && (node.Tag == "!!str" || node.Tag == "") {
return node.Value, true
}
return "", false
}

func (o *MapVariant) getVariant(key string) (variant, bool) {
valueNode, found := getValueNode(o.node, key)
if !found {
Expand All @@ -102,58 +104,31 @@ func (o *MapVariant) getVariant(key string) (variant, bool) {
return v, true
}

func getValueNode(m *yaml.Node, key string) (*yaml.Node, bool) {
children := m.Content
if len(children)%2 != 0 {
log.Fatalf("unexpected number of children for map %d", len(children))
}

for i := 0; i < len(children); i += 2 {
keyNode := children[i]

k, ok := asString(keyNode)
if ok && k == key {
valueNode := children[i+1]
return valueNode, true
}
func (o *MapVariant) setField(key string, val variant) {
newNode := val.Node()
i := findMapKey(o.node, key)
if i >= 0 {
// update existing field
deepCopyFormatting(o.node.Content[i+1], newNode)
o.node.Content[i+1] = newNode
} else {
// insert new field at the end
o.node.Content = append(o.node.Content, buildStringNode(key), newNode)
}
return nil, false
}

func (o *MapVariant) set(key string, val variant) {
o.setYAMLNode(key, val.Node())
func (o *MapVariant) Set(newValue *MapVariant) {
newNode := newValue.Node()
deepCopyFormatting(o.node, newNode)
o.node.Content = newNode.Content
}

func (o *MapVariant) setYAMLNode(key string, node *yaml.Node) {
children := o.node.Content
if len(children)%2 != 0 {
log.Fatalf("unexpected number of children for map %d", len(children))
}

for i := 0; i < len(children); i += 2 {
keyNode := children[i]

k, ok := asString(keyNode)
if ok && k == key {
// TODO: Copy comments?
oldNode := children[i+1]
children[i+1] = node
children[i+1].FootComment = oldNode.FootComment
children[i+1].HeadComment = oldNode.HeadComment
children[i+1].LineComment = oldNode.LineComment
return
}
}

o.node.Content = append(o.node.Content, buildStringNode(key), node)
}

func (o *MapVariant) remove(key string) (bool, error) {
func (o *MapVariant) remove(key string) bool {
removed := false

children := o.node.Content
if len(children)%2 != 0 {
return false, fmt.Errorf("unexpected number of children for map %d", len(children))
log.Fatalf("couldn't remove field %q from map node: unexpected odd number of children (%d)", key, len(children))
}

var keep []*yaml.Node
Expand All @@ -171,7 +146,7 @@ func (o *MapVariant) remove(key string) (bool, error) {

o.node.Content = keep

return removed, nil
return removed
}

// remove field metadata.creationTimestamp when it's null.
Expand Down Expand Up @@ -275,11 +250,26 @@ func (nodes yamlKeyValuePairs) Swap(i, j int) { nodes[i], nodes[j] = nodes[j], n
// otherwise it will insert a map at the specified field.
// Note that if the value exists but is not a map, it will be replaced with a map.
func (o *MapVariant) UpsertMap(field string) *MapVariant {
m := o.GetMap(field)
if m != nil {
return m
node, found := o.getVariant(field)

if found {
switch node := node.(type) {
case *MapVariant:
// field was found and it is a map
if !node.IsEmpty() {
return node
}
// if the map is empty, replace it with a new map
// this supposed to result in better formatting if the map value is specified as {}
_ = o.remove(field)

default:
// field was found and it is NOT a map
_ = o.remove(field)
}
}

// insert map at field
keyNode := &yaml.Node{
Kind: yaml.ScalarNode,
Value: field,
Expand Down
12 changes: 6 additions & 6 deletions go/fn/internal/maphelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (o *MapVariant) SetNestedValue(val variant, fields ...string) error {
var err error
for i := 0; i < n; i++ {
if i == n-1 {
current.set(fields[i], val)
current.setField(fields[i], val)
} else {
current, _, err = current.getMap(fields[i], true)
if err != nil {
Expand Down Expand Up @@ -180,19 +180,19 @@ func (o *MapVariant) SetNestedFloat(f float64, fields ...string) error {
return o.SetNestedValue(newFloatScalarVariant(f), fields...)
}

func (o *MapVariant) GetNestedSlice(fields ...string) (*sliceVariant, bool, error) {
func (o *MapVariant) GetNestedSlice(fields ...string) (*SliceVariant, bool, error) {
node, found, err := o.GetNestedValue(fields...)
if err != nil || !found {
return nil, found, err
}
nodeS, ok := node.(*sliceVariant)
nodeS, ok := node.(*SliceVariant)
if !ok {
return nil, found, fmt.Errorf("incorrect type, was %T", node)
}
return nodeS, found, err
}

func (o *MapVariant) SetNestedSlice(s *sliceVariant, fields ...string) error {
func (o *MapVariant) SetNestedSlice(s *SliceVariant, fields ...string) error {
return o.SetNestedValue(s, fields...)
}

Expand All @@ -206,13 +206,13 @@ func (o *MapVariant) RemoveNestedField(fields ...string) (bool, error) {
}

if i == n-1 {
return current.remove(fields[i])
return current.remove(fields[i]), nil
}
switch entry := entry.(type) {
case *MapVariant:
current = entry
default:
return false, fmt.Errorf("value is of unexpected type %T", entry)
return false, fmt.Errorf("removeNestedField: value is expected to be map, but is of unexpected type %T", entry)
}
}
return false, fmt.Errorf("unexpected code reached")
Expand Down
4 changes: 2 additions & 2 deletions go/fn/internal/scalar.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ type scalarVariant struct {
node *yaml.Node
}

func (v *scalarVariant) GetKind() variantKind {
return variantKindScalar
func (v *scalarVariant) GetKind() VariantKind {
return VariantKindScalar
}

func newStringScalarVariant(s string) *scalarVariant {
Expand Down
18 changes: 9 additions & 9 deletions go/fn/internal/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,34 @@ import (
"sigs.k8s.io/kustomize/kyaml/yaml"
)

type sliceVariant struct {
type SliceVariant struct {
node *yaml.Node
}

func NewSliceVariant(s ...variant) *sliceVariant {
func NewSliceVariant(s ...variant) *SliceVariant {
node := buildSequenceNode()
for _, v := range s {
node.Content = append(node.Content, v.Node())
}
return &sliceVariant{node: node}
return &SliceVariant{node: node}
}

func (v *sliceVariant) GetKind() variantKind {
return variantKindSlice
func (v *SliceVariant) GetKind() VariantKind {
return VariantKindSlice
}

func (v *sliceVariant) Node() *yaml.Node {
func (v *SliceVariant) Node() *yaml.Node {
return v.node
}

func (v *sliceVariant) Clear() {
func (v *SliceVariant) Clear() {
v.node.Content = nil
}

func (v *sliceVariant) Elements() ([]*MapVariant, error) {
func (v *SliceVariant) Elements() ([]*MapVariant, error) {
return ExtractObjects(v.node.Content...)
}

func (v *sliceVariant) Add(node variant) {
func (v *SliceVariant) Add(node variant) {
v.node.Content = append(v.node.Content, node.Node())
}
16 changes: 8 additions & 8 deletions go/fn/internal/variant.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ import (
"sigs.k8s.io/kustomize/kyaml/yaml"
)

type variantKind string
type VariantKind string

const (
variantKindMap variantKind = "Map"
variantKindSlice variantKind = "Slice"
variantKindScalar variantKind = "Scalar"
VariantKindMap VariantKind = "Map"
VariantKindSlice VariantKind = "Slice"
VariantKindScalar VariantKind = "Scalar"
)

type variant interface {
GetKind() variantKind
GetKind() VariantKind
Node() *yaml.Node
}

Expand Down Expand Up @@ -90,7 +90,7 @@ func toVariant(n *yaml.Node) variant {
case yaml.MappingNode:
return &MapVariant{node: n}
case yaml.SequenceNode:
return &sliceVariant{node: n}
return &SliceVariant{node: n}

default:
panic("unhandled yaml node kind")
Expand Down Expand Up @@ -150,7 +150,7 @@ func TypedObjectToMapVariant(v interface{}) (*MapVariant, error) {
return mv, err
}

func TypedObjectToSliceVariant(v interface{}) (*sliceVariant, error) {
func TypedObjectToSliceVariant(v interface{}) (*SliceVariant, error) {
// The built-in types only have json tags. We can't simply do ynode.Encode(v),
// since it use the lowercased field name by default if no yaml tag is specified.
// This affects both k8s built-in types (e.g. appsv1.Deployment) and any types
Expand All @@ -177,7 +177,7 @@ func TypedObjectToSliceVariant(v interface{}) (*sliceVariant, error) {
return nil, fmt.Errorf("unable to convert strong typed object to yaml node: %w", err)
}

return &sliceVariant{node: node}, nil
return &SliceVariant{node: node}, nil
}

func MapVariantToTypedObject(mv *MapVariant, ptr interface{}) error {
Expand Down
Loading