Skip to content

Commit

Permalink
refactor: kube package and remove the kpt deps
Browse files Browse the repository at this point in the history
Signed-off-by: peefy <xpf6677@163.com>
  • Loading branch information
Peefy committed May 8, 2024
1 parent 0977da1 commit 4374640
Show file tree
Hide file tree
Showing 12 changed files with 284 additions and 180 deletions.
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ module kcl-lang.io/krm-kcl
go 1.21

require (
github.com/GoogleContainerTools/kpt-functions-sdk/go/fn v0.0.0-20230427202446-3255accc518d
github.com/hashicorp/go-getter v1.7.4
github.com/stretchr/testify v1.9.0
k8s.io/api v0.28.3
k8s.io/apimachinery v0.28.3
kcl-lang.io/cli v0.8.7
kcl-lang.io/kpm v0.8.6
Expand All @@ -23,7 +21,6 @@ require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/GoogleContainerTools/kpt-functions-sdk/go/api v0.0.0-20220720212527-133180134b93 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
Expand Down Expand Up @@ -166,6 +163,7 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.28.3 // indirect
k8s.io/apiextensions-apiserver v0.24.1 // indirect
k8s.io/client-go v0.28.3 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GoogleContainerTools/kpt-functions-sdk/go/api v0.0.0-20220720212527-133180134b93 h1:c1GhPzYzU2a3E+/2WB9OxoljK2pNYfsBF5Fz9GkdYXs=
github.com/GoogleContainerTools/kpt-functions-sdk/go/api v0.0.0-20220720212527-133180134b93/go.mod h1:gkK43tTaPXFNASpbIbQImzhmt1hdcdin++kvzTblykc=
github.com/GoogleContainerTools/kpt-functions-sdk/go/fn v0.0.0-20230427202446-3255accc518d h1:kgC/R6Kl+tBjsRvcPr4Beae1MiHumNMtbmUTy7qlPZI=
github.com/GoogleContainerTools/kpt-functions-sdk/go/fn v0.0.0-20230427202446-3255accc518d/go.mod h1:Pnd3ImgaWS3OBVjztSiGMACMf+CDs20l5nT5Oljy/tA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
Expand Down
1 change: 0 additions & 1 deletion pkg/api/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ type MatchConstraintsSpec struct {

// ResourceRule defines a rule for matching resources.
type ResourceRule struct {
APIGroups []string `json:"apiGroups,omitempty" yaml:"apiGroups,omitempty"`
APIVersions []string `json:"apiVersions,omitempty" yaml:"apiVersions,omitempty"`
Kinds []string `json:"kinds,omitempty" yaml:"kinds,omitempty"`
}
50 changes: 18 additions & 32 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"fmt"
"os"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/kustomize/kyaml/yaml"

Expand All @@ -14,6 +12,7 @@ import (
"kcl-lang.io/krm-kcl/pkg/api"
"kcl-lang.io/krm-kcl/pkg/api/v1alpha1"
"kcl-lang.io/krm-kcl/pkg/edit"
"kcl-lang.io/krm-kcl/pkg/kube"
src "kcl-lang.io/krm-kcl/pkg/source"
)

Expand Down Expand Up @@ -52,36 +51,23 @@ type KCLRun struct {
// Config is used to configure the KCLRun instance based on the given FunctionConfig.
// It converts ConfigMap to KCLRun or assigns values directly from KCLRun.
// If an error occurs during the configuration process, an error message will be returned.
func (r *KCLRun) Config(fnCfg *fn.KubeObject) error {
fnCfgKind := fnCfg.GetKind()
fnCfgAPIVersion := fnCfg.GetAPIVersion()
func (r *KCLRun) Config(o *kube.KubeObject) error {
if o == nil {
return fmt.Errorf("FunctionConfig is missing. Expect `ConfigMap` or `KCLRun`")
}
kind := o.GetKind()
apiVersion := o.GetAPIVersion()
switch {
case fnCfg.IsEmpty():
case o.IsNilOrEmpty():
return fmt.Errorf("FunctionConfig is missing. Expect `ConfigMap` or `KCLRun`")
case fnCfgAPIVersion == ConfigMapAPIVersion && fnCfgKind == ConfigMapKind:
cm := &corev1.ConfigMap{}
if err := fnCfg.As(cm); err != nil {
return err
}
// Convert ConfigMap to KCLRun
r.Name = cm.Name
r.Namespace = cm.Namespace
r.Spec.Params = map[string]interface{}{}
for k, v := range cm.Data {
if k == api.SourceKey {
r.Spec.Source = v
}
r.Spec.Params[k] = v
}
case fnCfgAPIVersion == v1alpha1.KCLRunAPIVersion && fnCfgKind == api.KCLRunKind:
if err := fnCfg.As(r); err != nil {
case apiVersion == v1alpha1.KCLRunAPIVersion && kind == api.KCLRunKind:
if err := o.As(r); err != nil {
return err
}
default:
return fmt.Errorf("`functionConfig` must be either %v or %v, but we got: %v",
schema.FromAPIVersionAndKind(ConfigMapAPIVersion, ConfigMapKind).String(),
return fmt.Errorf("`functionConfig` must be either %v, but we got: %v",
schema.FromAPIVersionAndKind(v1alpha1.KCLRunAPIVersion, api.KCLRunKind).String(),
schema.FromAPIVersionAndKind(fnCfg.GetAPIVersion(), fnCfg.GetKind()).String())
schema.FromAPIVersionAndKind(apiVersion, kind).String())
}

// Defaulting
Expand Down Expand Up @@ -112,16 +98,16 @@ func (r *KCLRun) Run() ([]*yaml.RNode, error) {
// It parses the FunctionConfig and each object in the ResourceList, transforms them according to the KCLRun configuration,
// and updates the ResourceList with the transformed objects.
// If an error occurs during the transformation process, an error message will be returned.
func (r *KCLRun) TransformResourceList(rl *fn.ResourceList) error {
var transformedObjects []*fn.KubeObject
func (r *KCLRun) TransformResourceList(rl *kube.ResourceList) error {
var transformedObjects []*kube.KubeObject
var nodes []*yaml.RNode

fcRN, err := yaml.Parse(rl.FunctionConfig.String())
fcRN, err := yaml.Parse(rl.FunctionConfig.MustString())
if err != nil {
return err
}
for _, obj := range rl.Items {
objRN, err := yaml.Parse(obj.String())
objRN, err := yaml.Parse(obj.MustString())
if err != nil {
return err
}
Expand All @@ -132,7 +118,7 @@ func (r *KCLRun) TransformResourceList(rl *fn.ResourceList) error {
return err
}
for _, n := range transformedNodes {
obj, err := fn.ParseKubeObject([]byte(n.MustString()))
obj, err := kube.ParseKubeObject([]byte(n.MustString()))
if err != nil {
return err
}
Expand All @@ -146,7 +132,7 @@ func (r *KCLRun) TransformResourceList(rl *fn.ResourceList) error {
func (c *KCLRun) Transform(in []*yaml.RNode, fnCfg *yaml.RNode) ([]*yaml.RNode, error) {
var filterNodes []*yaml.RNode
for _, n := range in {
obj, err := fn.ParseKubeObject([]byte(n.MustString()))
obj, err := kube.ParseKubeObject([]byte(n.MustString()))
if err != nil {
return nil, err
}
Expand Down
38 changes: 3 additions & 35 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package config
import (
"testing"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"github.com/stretchr/testify/assert"
"kcl-lang.io/krm-kcl/pkg/kube"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

Expand Down Expand Up @@ -50,44 +50,12 @@ spec:
`,
expectErrMsg: "",
},
{
name: "valid ConfigMap",
config: `apiVersion: v1
kind: ConfigMap
metadata:
name: my-kcl-fn
data:
source: |
# Set namespace to "baz"
[item | {metadata.namespace = "baz"} for item in option("resource_list")]
`,
},
{
name: "ConfigMap missing source",
config: `apiVersion: v1
kind: ConfigMap
metadata:
name: my-kcl-fn
`,
expectErrMsg: "`source` must not be empty",
},
{
name: "ConfigMap with parameter but missing source",
config: `apiVersion: v1
kind: ConfigMap
metadata:
name: my-kcl-fn
data:
param1: foo
`,
expectErrMsg: "`source` must not be empty",
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
r := &KCLRun{}
ko, err := fn.ParseKubeObject([]byte(tc.config))
ko, err := kube.ParseKubeObject([]byte(tc.config))
assert.NoError(t, err)
err = r.Config(ko)
if tc.expectErrMsg == "" {
Expand Down Expand Up @@ -180,7 +148,7 @@ spec:
tc := tc
t.Run(tc.name, func(t *testing.T) {
r := &KCLRun{}
ko, err := fn.ParseKubeObject([]byte(tc.config))
ko, err := kube.ParseKubeObject([]byte(tc.config))
assert.NoError(t, err)
err = r.Config(ko)
assert.NoError(t, err)
Expand Down
7 changes: 3 additions & 4 deletions pkg/config/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@ package config
import (
"strings"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"kcl-lang.io/krm-kcl/pkg/api"
"kcl-lang.io/krm-kcl/pkg/kube"
)

// MatchResourceRules checks if the given Kubernetes object matches the resource rules specified in KCLRun.
func MatchResourceRules(obj *fn.KubeObject, MatchConstraints *api.MatchConstraintsSpec) bool {
func MatchResourceRules(obj *kube.KubeObject, MatchConstraints *api.MatchConstraintsSpec) bool {
// if MatchConstraints.ResourceRules is not set (nil or empty), return true by default
if len(MatchConstraints.ResourceRules) == 0 {
return true
}
// iterate through each resource rule
for _, rule := range MatchConstraints.ResourceRules {
if containsString(rule.APIGroups, obj.GroupKind().Group) &&
containsString(rule.APIVersions, obj.GetAPIVersion()) &&
if containsString(rule.APIVersions, obj.GetAPIVersion()) &&
containsString(rule.Kinds, obj.GetKind()) {
return true
}
Expand Down
139 changes: 139 additions & 0 deletions pkg/kube/object.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package kube

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

"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

// KubeObject represents a Kubernetes object with associated functions.
type KubeObject struct {
node *yaml.RNode
}

// Node returns the underlying RNode of the Kubernetes object.
func (o *KubeObject) Node() *yaml.RNode {
return o.node
}

// IsNilOrEmpty checks if the Kubernetes object is either nil or empty.
func (o *KubeObject) IsNilOrEmpty() bool {
return o.node.IsNilOrEmpty()
}

// GetAPIVersion retrieves the APIVersion of the Kubernetes object.
func (o *KubeObject) GetAPIVersion() string {
return o.node.GetApiVersion()
}

// GetName retrieves the name of the Kubernetes object.
func (o *KubeObject) GetName() string {
return o.node.GetName()
}

// GetNamespace retrieves the namespace of the Kubernetes object.
func (o *KubeObject) GetNamespace() string {
return o.node.GetNamespace()
}

// GetKind retrieves the kind of the Kubernetes object.
func (o *KubeObject) GetKind() string {
return o.node.GetKind()
}

// As unmarshals the Kubernetes object into a Go struct pointed by ptr.
func (o *KubeObject) As(ptr interface{}) error {
if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Ptr {
return fmt.Errorf("ptr must be a pointer to an object")
}
j, err := o.node.MarshalJSON()
if err != nil {
return err
}
err = json.Unmarshal(j, ptr)
if err != nil {
return err
}
return nil
}

// GetNestedMap retrieves a nested field from a Kubernetes object
// as a KubeObject. The field must be a map type.
func (o *KubeObject) GetNestedMap(field string) (*KubeObject, error) {
node, err := o.node.Pipe(yaml.Lookup(field))
if err != nil {
return nil, err
}
return &KubeObject{node}, nil
}

// GetNestedSlice retrieves a nested field from Kubernetes object
// as a slice of KubeObjects. The field must be a slice type.
func (o *KubeObject) GetNestedSlice(field string) (KubeObjects, error) {
node, err := o.node.Pipe(yaml.Lookup(field))
if err != nil {
return nil, err
}
nodes, err := node.Elements()
if err != nil {
return nil, err
}
var kubeObjects []*KubeObject
for _, node := range nodes {
kubeObjects = append(kubeObjects, &KubeObject{node})
}
return kubeObjects, nil
}

// MustString returns the YAML string representation of the Kubernetes object.
// It panics if any error occurs while encoding to YAML.
func (o *KubeObject) MustString() string {
return o.node.MustString()
}

// ParseKubeObjects parses a YAML byte slice into a slice of KubeObjects.
func ParseKubeObjects(in []byte) ([]*KubeObject, error) {
reader := kio.ByteReader{Reader: bytes.NewReader(in)}
nodes, err := reader.Read()
if err != nil {
return nil, err
}
var kubeObjects []*KubeObject
for _, node := range nodes {
kubeObjects = append(kubeObjects, &KubeObject{node})
}
return kubeObjects, nil
}

// ParseKubeObject parses a single YAML object from a byte slice into a KubeObject.
func ParseKubeObject(in []byte) (*KubeObject, error) {
node, err := yaml.Parse(string(in))
if err != nil {
return nil, err
}
return &KubeObject{node}, nil
}

// KubeObjects defines a slice of KubeObject types with convenience methods.
type KubeObjects []*KubeObject

// Len returns the number of KubeObjects.
func (o KubeObjects) Len() int { return len(o) }

// Swap switches the positions of two KubeObjects in the slice.
func (o KubeObjects) Swap(i, j int) { o[i], o[j] = o[j], o[i] }

// MustString concatenates all KubeObjects into a single YAML string,
// separated by '---' (YAML document separator).
func (o KubeObjects) MustString() string {
var elems []string
for _, obj := range o {
elems = append(elems, strings.TrimSpace(obj.MustString()))
}
return strings.Join(elems, "\n---\n")
}
Loading

0 comments on commit 4374640

Please sign in to comment.