Skip to content

Commit

Permalink
feat: kcl run config
Browse files Browse the repository at this point in the history
Signed-off-by: peefy <xpf6677@163.com>
  • Loading branch information
Peefy committed May 7, 2024
1 parent f5c3fd3 commit 0b1dae0
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 94 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ require (
k8s.io/api v0.28.3
k8s.io/apimachinery v0.28.3
kcl-lang.io/cli v0.8.7
kcl-lang.io/kcl-go v0.8.6
kcl-lang.io/kpm v0.8.6
sigs.k8s.io/kustomize/kyaml v0.16.0
)
Expand Down Expand Up @@ -172,6 +171,7 @@ require (
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
kcl-lang.io/kcl-go v0.8.6 // indirect
kcl-lang.io/kcl-openapi v0.6.1 // indirect
kcl-lang.io/lib v0.8.6 // indirect
oras.land/oras-go v1.2.3 // indirect
Expand Down
62 changes: 62 additions & 0 deletions pkg/api/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package api

const (
// KCLRunKind represents the kind of resource for the KCLRun resource.
KCLRunKind = "KCLRun"

// SourceKey is the key for the source field in the KCLRun resource, which denotes the source of the KCL script
// and it can be from the inline code, git, oci source, etc.
SourceKey = "source"

// ParamsKey is the key for the params field in the KCLRun resource, which denotes the top level dynamic arguments.
ParamsKey = "params"

// ConfigKey is the key for the config field in the KCLRun resource, which denotes the KCL CLI running config.
ConfigKey = "config"

// MatchConstraintsKey is the key for the match constraints field in the KCLRun resource.
MatchConstraintsKey = "matchConstraints"
)

// ConfigSpec defines the compile config.
type ConfigSpec struct {
// Arguments is the list of top level dynamic arguments for the kcl option function, e.g., env="prod"
Arguments []string `json:"arguments" yaml:"arguments"`
// Settings is the list of kcl setting files including all of the CLI config.
Settings []string `json:"settings" yaml:"settings"`
// Overrides is the list of override paths and values, e.g., app.image="v2"
Overrides []string `json:"overrides" yaml:"overrides"`
// PathSelectors is the list of path selectors to select output result, e.g., a.b.c
PathSelectors []string `json:"pathSelectors" yaml:"pathSelectors"`
// Vendor denotes running kcl in the vendor mode.
Vendor bool `json:"vendor" yaml:"vendor"`
// SortKeys denotes sorting the output result keys, e.g., `{b = 1, a = 2} => {a = 2, b = 1}`.
SortKeys bool `json:"sortKeys" yaml:"sortKeys"`
// ShowHidden denotes output the hidden attribute in the result.
ShowHidden bool `json:"showHidden" yaml:"showHidden"`
// DisableNone denotes running kcl and disable dumping None values.
DisableNone bool `json:"disableNone" yaml:"disableNone"`
// Debug denotes running kcl in debug mode.
Debug bool `json:"debug" yaml:"debug"`
// StrictRangeCheck performs the 32-bit strict numeric range checks on numbers.
StrictRangeCheck bool `json:"strictRangeCheck" yaml:"strictRangeCheck"`
}

// CredSpec defines authentication credentials for remote locations
type CredSpec struct {
Url string `json:"url" yaml:"url"`
Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
}

// MatchConstraintsSpec defines the resource matching rules.
type MatchConstraintsSpec struct {
ResourceRules []ResourceRule `json:"resourceRules,omitempty" yaml:"resourceRules,omitempty"`
}

// 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"`
}
12 changes: 0 additions & 12 deletions pkg/api/v1alpha1/kclrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,4 @@ const (

// KCLRunAPIVersion is a combination of the API group and version for the KCLRun resource.
KCLRunAPIVersion = KCLRunGroup + "/" + KCLRunVersion

// KCLRunKind represents the kind of resource for the KCLRun resource.
KCLRunKind = "KCLRun"

// SourceKey is the key for the source field in the KCLRun resource.
SourceKey = "source"

// ParamsKey is the key for the params field in the KCLRun resource.
ParamsKey = "params"

// MatchConstraintsKey is the key for the match constraints field in the KCLRun resource.
MatchConstraintsKey = "matchConstraints"
)
101 changes: 28 additions & 73 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package config
import (
"fmt"
"os"
"strings"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
corev1 "k8s.io/api/core/v1"
Expand All @@ -12,6 +11,7 @@ import (

"kcl-lang.io/kpm/pkg/client"
"kcl-lang.io/kpm/pkg/settings"
"kcl-lang.io/krm-kcl/pkg/api"
"kcl-lang.io/krm-kcl/pkg/api/v1alpha1"
"kcl-lang.io/krm-kcl/pkg/edit"
src "kcl-lang.io/krm-kcl/pkg/source"
Expand All @@ -38,34 +38,17 @@ type KCLRun struct {
Spec struct {
// Source is a required field for providing a KCL script inline.
Source string `json:"source" yaml:"source"`
// Config is the compile config.
Config api.ConfigSpec `json:"config" yaml:"config"`
// Credentials for remote locations
Credentials CredSpec `json:"credentials" yaml:"credentials"`
Credentials api.CredSpec `json:"credentials" yaml:"credentials"`
// Params are the parameters in key-value pairs format.
Params map[string]interface{} `json:"params,omitempty" yaml:"params,omitempty"`
// MatchConstraints defines the resource matching rules.
MatchConstraints MatchConstraints `json:"matchConstraints,omitempty" yaml:"matchConstraints,omitempty"`
MatchConstraints api.MatchConstraintsSpec `json:"matchConstraints,omitempty" yaml:"matchConstraints,omitempty"`
} `json:"spec" yaml:"spec"`
}

// CredSpec defines authentication credentials for remote locations
type CredSpec struct {
Url string `json:"url" yaml:"url"`
Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
}

// MatchConstraints defines the resource matching rules.
type MatchConstraints struct {
ResourceRules []ResourceRule `json:"resourceRules,omitempty" yaml:"resourceRules,omitempty"`
}

// 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"`
}

// 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.
Expand All @@ -85,19 +68,19 @@ func (r *KCLRun) Config(fnCfg *fn.KubeObject) error {
r.Namespace = cm.Namespace
r.Spec.Params = map[string]interface{}{}
for k, v := range cm.Data {
if k == v1alpha1.SourceKey {
if k == api.SourceKey {
r.Spec.Source = v
}
r.Spec.Params[k] = v
}
case fnCfgAPIVersion == v1alpha1.KCLRunAPIVersion && fnCfgKind == v1alpha1.KCLRunKind:
case fnCfgAPIVersion == v1alpha1.KCLRunAPIVersion && fnCfgKind == api.KCLRunKind:
if err := fnCfg.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(),
schema.FromAPIVersionAndKind(v1alpha1.KCLRunAPIVersion, v1alpha1.KCLRunKind).String(),
schema.FromAPIVersionAndKind(v1alpha1.KCLRunAPIVersion, api.KCLRunKind).String(),
schema.FromAPIVersionAndKind(fnCfg.GetAPIVersion(), fnCfg.GetKind()).String())
}

Expand All @@ -112,6 +95,19 @@ func (r *KCLRun) Config(fnCfg *fn.KubeObject) error {
return nil
}

// Run is used to output the YAML list with the KCLRun instance.
func (r *KCLRun) Run() ([]*yaml.RNode, error) {
c, err := yaml.Marshal(r)
if err != nil {
return nil, err
}
fnCfg, err := yaml.Parse(string(c))
if err != nil {
return nil, err
}
return r.Transform(nil, fnCfg)
}

// TransformResourceList is used to transform the ResourceList with the KCLRun instance.
// 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.
Expand Down Expand Up @@ -162,14 +158,14 @@ func (c *KCLRun) Transform(in []*yaml.RNode, fnCfg *yaml.RNode) ([]*yaml.RNode,
c.DealAnnotations()

// Authenticate with credentials to remote source
if os.Getenv("KCL_SRC_URL") != "" {
c.Spec.Credentials.Url = os.Getenv("KCL_SRC_URL")
if os.Getenv(SrcUrlEnvVar) != "" {
c.Spec.Credentials.Url = os.Getenv(SrcUrlEnvVar)
}
if os.Getenv("KCL_SRC_USERNAME") != "" {
c.Spec.Credentials.Username = os.Getenv("KCL_SRC_USERNAME")
if os.Getenv(SrcUrlUsernameEnvVar) != "" {
c.Spec.Credentials.Username = os.Getenv(SrcUrlUsernameEnvVar)
}
if os.Getenv("KCL_SRC_PASSWORD") != "" {
c.Spec.Credentials.Password = os.Getenv("KCL_SRC_PASSWORD")
if os.Getenv(SrcUrlPasswordEnvVar) != "" {
c.Spec.Credentials.Password = os.Getenv(SrcUrlPasswordEnvVar)
}
if src.IsOCI(c.Spec.Source) && c.Spec.Credentials.Url != "" {
cli, err := client.NewKpmClient()
Expand All @@ -185,56 +181,15 @@ func (c *KCLRun) Transform(in []*yaml.RNode, fnCfg *yaml.RNode) ([]*yaml.RNode,
Name: DefaultProgramName,
Source: c.Spec.Source,
FunctionConfig: fnCfg,
Config: &c.Spec.Config,
}
return st.Transform(filterNodes)
}

// MatchResourceRules checks if the given Kubernetes object matches the resource rules specified in KCLRun.
func MatchResourceRules(obj *fn.KubeObject, MatchConstraints *MatchConstraints) 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()) &&
containsString(rule.Kinds, obj.GetKind()) {
return true
}
}
// if no match is found, return false
return false
}

// DealAnnotations handles annotations, e.g., allow-insecure-source.
func (r *KCLRun) DealAnnotations() {
// Deal the allow-insecure-source annotation
if v, ok := r.ObjectMeta.Annotations[AnnotationAllowInSecureSource]; ok && isOk(v) {
os.Setenv(settings.DEFAULT_OCI_PLAIN_HTTP_ENV, settings.ON)
}
}

// isOk checks if a given string is in the list of "OK" values.
func isOk(value string) bool {
okValues := []string{"ok", "yes", "true", "1", "on"}
for _, v := range okValues {
if strings.EqualFold(strings.ToLower(value), strings.ToLower(v)) {
return true
}
}
return false
}

// containsString checks if a slice contains a string or "*"
func containsString(slice []string, str string) bool {
if len(slice) == 0 {
return true
}
for _, s := range slice {
if s == "*" || s == str {
return true
}
}
return false
}
99 changes: 99 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

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

func TestKCLConfig(t *testing.T) {
Expand Down Expand Up @@ -98,3 +99,101 @@ data:
})
}
}

func TestKCLRun(t *testing.T) {
testcases := []struct {
name string
config string
expectResult string
expectErrMsg string
}{
{
name: "KCLRun0",
config: `apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: my-kcl-fn
namespace: foo
spec:
source: |
{
apiVersion = "v1"
}
`,
expectResult: `apiVersion: v1`,
},
{
name: "KCLRun1",
config: `apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: my-kcl-fn
namespace: foo
spec:
params:
version: v1
source: |
{
apiVersion = option("params")?.version
}
`,
expectResult: `apiVersion: v1`,
},
{
name: "KCLRun2",
config: `apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: my-kcl-fn
namespace: foo
spec:
config:
arguments:
- version=v1
source: |
{
apiVersion = option("version")
}
`,
expectResult: `apiVersion: v1`,
},
{
name: "KCLRun3",
config: `apiVersion: krm.kcl.dev/v1alpha1
kind: KCLRun
metadata:
name: my-kcl-fn
namespace: foo
spec:
config:
disableNone: true
source: |
{
a = None
b = 1
}
`,
expectResult: `b: 1`,
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
r := &KCLRun{}
ko, err := fn.ParseKubeObject([]byte(tc.config))
assert.NoError(t, err)
err = r.Config(ko)
assert.NoError(t, err)
result, err := r.Run()
if tc.expectErrMsg == "" {
assert.NoError(t, err)
resultYaml, err := yaml.Parse(tc.expectResult)
assert.NoError(t, err)
assert.Equal(t, result[0], resultYaml)
} else {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.expectErrMsg)
}
})
}
}
7 changes: 7 additions & 0 deletions pkg/config/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package config

const (
SrcUrlEnvVar = "KCL_SRC_URL"
SrcUrlUsernameEnvVar = "KCL_SRC_USERNAME"
SrcUrlPasswordEnvVar = "KCL_SRC_PASSWORD"
)
Loading

0 comments on commit 0b1dae0

Please sign in to comment.