From e91cdb5eba9dc09134cef5b5094fabaf3b71ebb3 Mon Sep 17 00:00:00 2001 From: jregan Date: Thu, 16 Jul 2020 17:14:24 -0700 Subject: [PATCH] Checkpoint --- api/go.mod | 1 + api/internal/merge/merginator.go | 25 ++ api/internal/merge/merginator_test.go | 4 + api/internal/validate/fieldvalidator.go | 64 ++++ api/internal/validate/fieldvalidator_test.go | 4 + api/internal/wrappy/factory.go | 41 +++ api/internal/wrappy/factory_test.go | 4 + api/internal/wrappy/wnode.go | 143 ++++++++ api/internal/wrappy/wnode_test.go | 339 ++++++++++++++++++ api/k8sdeps/validator/kustvalidator.go | 3 + api/krusty/internal/provider/depprovider.go | 183 ++++++++++ api/krusty/kustomizer.go | 24 +- api/krusty/options.go | 5 + kustomize/internal/commands/build/build.go | 2 + .../commands/build/flagEnableKyaml.go | 21 ++ 15 files changed, 851 insertions(+), 12 deletions(-) create mode 100644 api/internal/merge/merginator.go create mode 100644 api/internal/merge/merginator_test.go create mode 100644 api/internal/validate/fieldvalidator.go create mode 100644 api/internal/validate/fieldvalidator_test.go create mode 100644 api/internal/wrappy/factory.go create mode 100644 api/internal/wrappy/factory_test.go create mode 100644 api/internal/wrappy/wnode.go create mode 100644 api/internal/wrappy/wnode_test.go create mode 100644 api/krusty/internal/provider/depprovider.go create mode 100644 kustomize/internal/commands/build/flagEnableKyaml.go diff --git a/api/go.mod b/api/go.mod index 45ebb6b925..26a56b767a 100644 --- a/api/go.mod +++ b/api/go.mod @@ -12,6 +12,7 @@ require ( github.com/yujunz/go-getter v1.4.1-lite golang.org/x/tools v0.0.0-20191010075000-0337d82405ff gopkg.in/yaml.v2 v2.3.0 + gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 k8s.io/api v0.17.0 k8s.io/apimachinery v0.17.0 k8s.io/client-go v0.17.0 diff --git a/api/internal/merge/merginator.go b/api/internal/merge/merginator.go new file mode 100644 index 0000000000..2954508891 --- /dev/null +++ b/api/internal/merge/merginator.go @@ -0,0 +1,25 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package merge + +import ( + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/resource" +) + +// Merginator implements resmap.Merginator using kyaml libs. +type Merginator struct { +} + +var _ resmap.Merginator = (*Merginator)(nil) + +func NewMerginator(_ *resource.Factory) *Merginator { + return &Merginator{} +} + +// Merge implements resmap.Merginator +func (m Merginator) Merge( + resources []*resource.Resource) (resmap.ResMap, error) { + panic("TODO(#Merginator): implement Merge") +} diff --git a/api/internal/merge/merginator_test.go b/api/internal/merge/merginator_test.go new file mode 100644 index 0000000000..3a15de64b8 --- /dev/null +++ b/api/internal/merge/merginator_test.go @@ -0,0 +1,4 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package merge_test diff --git a/api/internal/validate/fieldvalidator.go b/api/internal/validate/fieldvalidator.go new file mode 100644 index 0000000000..c9e47fa3e3 --- /dev/null +++ b/api/internal/validate/fieldvalidator.go @@ -0,0 +1,64 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "sigs.k8s.io/kustomize/api/ifc" +) + +// FieldValidator implements ifc.Validator to check +// the values of various KRM string fields, +// e.g. labels, annotations, names, namespaces. +type FieldValidator struct { +} + +var _ ifc.Validator = (*FieldValidator)(nil) + +func NewFieldValidator() *FieldValidator { + return &FieldValidator{} +} + +// TODO(#FieldValidator): implement MakeAnnotationValidator +func (f FieldValidator) MakeAnnotationValidator() func(map[string]string) error { + return func(x map[string]string) error { + return nil + } +} + +// TODO(#FieldValidator): implement MakeAnnotationNameValidator +func (f FieldValidator) MakeAnnotationNameValidator() func([]string) error { + return func(x []string) error { + return nil + } +} + +// TODO(#FieldValidator): implement MakeLabelValidator +func (f FieldValidator) MakeLabelValidator() func(map[string]string) error { + return func(x map[string]string) error { + return nil + } +} + +// TODO(#FieldValidator): implement MakeLabelNameValidator +func (f FieldValidator) MakeLabelNameValidator() func([]string) error { + return func(x []string) error { + return nil + } +} + +// TODO(#FieldValidator): implement ValidateNamespace +func (f FieldValidator) ValidateNamespace(s string) []string { + var errs []string + return errs +} + +// TODO(#FieldValidator): implement ErrIfInvalidKey +func (f FieldValidator) ErrIfInvalidKey(s string) error { + return nil +} + +// TODO(#FieldValidator): implement IsEnvVarName +func (f FieldValidator) IsEnvVarName(k string) error { + return nil +} diff --git a/api/internal/validate/fieldvalidator_test.go b/api/internal/validate/fieldvalidator_test.go new file mode 100644 index 0000000000..82f18775b2 --- /dev/null +++ b/api/internal/validate/fieldvalidator_test.go @@ -0,0 +1,4 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package validate_test diff --git a/api/internal/wrappy/factory.go b/api/internal/wrappy/factory.go new file mode 100644 index 0000000000..b03b9e8fc1 --- /dev/null +++ b/api/internal/wrappy/factory.go @@ -0,0 +1,41 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package wrappy + +import ( + "sigs.k8s.io/kustomize/api/ifc" + "sigs.k8s.io/kustomize/api/types" +) + +// WNodeFactory makes instances of WNode. +// These instances in turn adapt +// sigs.k8s.io/kustomize/kyaml/yaml.RNode +// to implement ifc.Unstructured. +// This factory is meant to implement ifc.KunstructuredFactory. +type WNodeFactory struct { +} + +var _ ifc.KunstructuredFactory = (*WNodeFactory)(nil) + +func (k *WNodeFactory) SliceFromBytes(bs []byte) ([]ifc.Kunstructured, error) { + panic("TODO(#WNodeFactory): implement SliceFromBytes") +} + +func (k *WNodeFactory) FromMap(m map[string]interface{}) ifc.Kunstructured { + panic("TODO(#WNodeFactory): implement FromMap") +} + +func (k *WNodeFactory) Hasher() ifc.KunstructuredHasher { + panic("TODO(#WNodeFactory): implement Hasher") +} + +func (k *WNodeFactory) MakeConfigMap( + kvLdr ifc.KvLoader, args *types.ConfigMapArgs) (ifc.Kunstructured, error) { + panic("TODO(#WNodeFactory): implement MakeConfigMap") +} + +func (k *WNodeFactory) MakeSecret( + kvLdr ifc.KvLoader, args *types.SecretArgs) (ifc.Kunstructured, error) { + panic("TODO(#WNodeFactory): implement MakeSecret") +} diff --git a/api/internal/wrappy/factory_test.go b/api/internal/wrappy/factory_test.go new file mode 100644 index 0000000000..7db433cb63 --- /dev/null +++ b/api/internal/wrappy/factory_test.go @@ -0,0 +1,4 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package wrappy_test diff --git a/api/internal/wrappy/wnode.go b/api/internal/wrappy/wnode.go new file mode 100644 index 0000000000..be3bb84856 --- /dev/null +++ b/api/internal/wrappy/wnode.go @@ -0,0 +1,143 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package wrappy + +import ( + "log" + + "sigs.k8s.io/kustomize/api/ifc" + "sigs.k8s.io/kustomize/api/resid" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// WNode implements ifc.Kunstructured using yaml.RNode. +// +// It exists only to help manage a switch from +// kunstruct.UnstructAdapter to yaml.RNode as the core +// representation of KRM objects in kustomize. +// +// It's got a silly name because we don't want it around for long, +// and want its use to be obvious. +type WNode struct { + node *yaml.RNode +} + +var _ ifc.Kunstructured = (*WNode)(nil) + +func NewWNode() *WNode { + return FromRNode(yaml.NewRNode(nil)) +} + +func FromRNode(node *yaml.RNode) *WNode { + return &WNode{node: node} +} + +func (wn *WNode) demandMetaData(label string) yaml.ResourceMeta { + meta, err := wn.node.GetMeta() + if err != nil { + // Log and die since interface doesn't allow error. + log.Fatalf("for %s', expected valid resource: %v", label, err) + } + return meta +} + +// Copy implements ifc.Kunstructured. +func (wn *WNode) Copy() ifc.Kunstructured { + return &WNode{node: wn.node.Copy()} +} + +// GetAnnotations implements ifc.Kunstructured. +func (wn *WNode) GetAnnotations() map[string]string { + return wn.demandMetaData("GetAnnotations").Annotations +} + +// GetFieldValue implements ifc.Kunstructured. +func (wn *WNode) GetFieldValue(path string) (interface{}, error) { + // The argument is a json path, e.g. "metadata.name" + // fields := strings.Split(path, ".") + // return wn.node.Pipe(yaml.Lookup(fields...)) + panic("TODO(#WNode): GetFieldValue; implement or drop from API") +} + +// GetGvk implements ifc.Kunstructured. +func (wn *WNode) GetGvk() resid.Gvk { + meta := wn.demandMetaData("GetGvk") + g, v := resid.ParseGroupVersion(meta.APIVersion) + return resid.Gvk{Group: g, Version: v, Kind: meta.Kind} +} + +// GetKind implements ifc.Kunstructured. +func (wn *WNode) GetKind() string { + return wn.demandMetaData("GetKind").Kind +} + +// GetLabels implements ifc.Kunstructured. +func (wn *WNode) GetLabels() map[string]string { + return wn.demandMetaData("GetLabels").Labels +} + +// GetName implements ifc.Kunstructured. +func (wn *WNode) GetName() string { + return wn.demandMetaData("GetName").Name +} + +// GetSlice implements ifc.Kunstructured. +func (wn *WNode) GetSlice(string) ([]interface{}, error) { + panic("TODO(#WNode) GetSlice; implement or drop from API") +} + +// GetSlice implements ifc.Kunstructured. +func (wn *WNode) GetString(string) (string, error) { + panic("TODO(#WNode) GetString; implement or drop from API") +} + +// Map implements ifc.Kunstructured. +func (wn *WNode) Map() map[string]interface{} { + panic("TODO(#WNode) Map; implement or drop from API") +} + +// MarshalJSON implements ifc.Kunstructured. +func (wn *WNode) MarshalJSON() ([]byte, error) { + return wn.node.MarshalJSON() +} + +// MatchesAnnotationSelector implements ifc.Kunstructured. +func (wn *WNode) MatchesAnnotationSelector(string) (bool, error) { + panic("TODO(#WNode) MatchesAnnotationSelector; implement or drop from API") +} + +// MatchesLabelSelector implements ifc.Kunstructured. +func (wn *WNode) MatchesLabelSelector(string) (bool, error) { + panic("TODO(#WNode) MatchesLabelSelector; implement or drop from API") +} + +// SetAnnotations implements ifc.Kunstructured. +func (wn *WNode) SetAnnotations(map[string]string) { + panic("TODO(#WNode) SetAnnotations; implement or drop from API") +} + +// SetGvk implements ifc.Kunstructured. +func (wn *WNode) SetGvk(resid.Gvk) { + panic("TODO(#WNode) SetGvk; implement or drop from API") +} + +// SetLabels implements ifc.Kunstructured. +func (wn *WNode) SetLabels(map[string]string) { + panic("TODO(#WNode) SetLabels; implement or drop from API") +} + +// SetName implements ifc.Kunstructured. +func (wn *WNode) SetName(string) { + panic("TODO(#WNode) SetName; implement or drop from API") +} + +// SetNamespace implements ifc.Kunstructured. +func (wn *WNode) SetNamespace(string) { + panic("TODO(#WNode) SetNamespace; implement or drop from API") +} + +// UnmarshalJSON implements ifc.Kunstructured. +func (wn *WNode) UnmarshalJSON(data []byte) error { + return wn.node.UnmarshalJSON(data) +} diff --git a/api/internal/wrappy/wnode_test.go b/api/internal/wrappy/wnode_test.go new file mode 100644 index 0000000000..072373a23f --- /dev/null +++ b/api/internal/wrappy/wnode_test.go @@ -0,0 +1,339 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package wrappy_test + +import ( + "strings" + "testing" + + "gopkg.in/yaml.v3" + . "sigs.k8s.io/kustomize/api/internal/wrappy" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" +) + +const ( + deploymentLittleJson = `{"apiVersion":"apps/v1","kind":"Deployment",` + + `"metadata":{"name":"homer","namespace":"simpsons"}}` + + deploymentBiggerJson = ` +{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "homer", + "namespace": "simpsons", + "labels": { + "fruit": "apple", + "veggie": "carrot" + }, + "annotations": { + "area": "51", + "greeting": "Take me to your leader." + } + } +} +` + bigMapYaml = `Kind: Service +complextree: + - field1: + - boolfield: true + floatsubfield: 1.01 + intsubfield: 1010 + stringsubfield: idx1010 + - boolfield: false + floatsubfield: 1.011 + intsubfield: 1011 + stringsubfield: idx1011 + field2: + - boolfield: true + floatsubfield: 1.02 + intsubfield: 1020 + stringsubfield: idx1020 + - boolfield: false + floatsubfield: 1.021 + intsubfield: 1021 + stringsubfield: idx1021 + - field1: + - boolfield: true + floatsubfield: 1.11 + intsubfield: 1110 + stringsubfield: idx1110 + - boolfield: false + floatsubfield: 1.111 + intsubfield: 1111 + stringsubfield: idx1111 + field2: + - boolfield: true + floatsubfield: 1.112 + intsubfield: 1120 + stringsubfield: idx1120 + - boolfield: false + floatsubfield: 1.1121 + intsubfield: 1121 + stringsubfield: idx1121 +metadata: + labels: + app: application-name + name: service-name +spec: + ports: + port: 80 +that: + - idx0 + - idx1 + - idx2 + - idx3 +these: + - field1: + - idx010 + - idx011 + field2: + - idx020 + - idx021 + - field1: + - idx110 + - idx111 + field2: + - idx120 + - idx121 + - field1: + - idx210 + - idx211 + field2: + - idx220 + - idx221 +this: + is: + aBool: true + aFloat: 1.001 + aNilValue: null + aNumber: 1000 + anEmptyMap: {} + anEmptySlice: [] +those: + - field1: idx0foo + field2: idx0bar + - field1: idx1foo + field2: idx1bar + - field1: idx2foo + field2: idx2bar +` +) + +func makeBigMap() map[string]interface{} { + return map[string]interface{}{ + "Kind": "Service", + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "app": "application-name", + }, + "name": "service-name", + }, + "spec": map[string]interface{}{ + "ports": map[string]interface{}{ + "port": int64(80), + }, + }, + "this": map[string]interface{}{ + "is": map[string]interface{}{ + "aNumber": int64(1000), + "aFloat": float64(1.001), + "aNilValue": nil, + "aBool": true, + "anEmptyMap": map[string]interface{}{}, + "anEmptySlice": []interface{}{}, + /* + TODO: test for unrecognizable (e.g. a function) + "unrecognizable": testing.InternalExample{ + Name: "fooBar", + }, + */ + }, + }, + "that": []interface{}{ + "idx0", + "idx1", + "idx2", + "idx3", + }, + "those": []interface{}{ + map[string]interface{}{ + "field1": "idx0foo", + "field2": "idx0bar", + }, + map[string]interface{}{ + "field1": "idx1foo", + "field2": "idx1bar", + }, + map[string]interface{}{ + "field1": "idx2foo", + "field2": "idx2bar", + }, + }, + "these": []interface{}{ + map[string]interface{}{ + "field1": []interface{}{"idx010", "idx011"}, + "field2": []interface{}{"idx020", "idx021"}, + }, + map[string]interface{}{ + "field1": []interface{}{"idx110", "idx111"}, + "field2": []interface{}{"idx120", "idx121"}, + }, + map[string]interface{}{ + "field1": []interface{}{"idx210", "idx211"}, + "field2": []interface{}{"idx220", "idx221"}, + }, + }, + "complextree": []interface{}{ + map[string]interface{}{ + "field1": []interface{}{ + map[string]interface{}{ + "stringsubfield": "idx1010", + "intsubfield": int64(1010), + "floatsubfield": float64(1.010), + "boolfield": true, + }, + map[string]interface{}{ + "stringsubfield": "idx1011", + "intsubfield": int64(1011), + "floatsubfield": float64(1.011), + "boolfield": false, + }, + }, + "field2": []interface{}{ + map[string]interface{}{ + "stringsubfield": "idx1020", + "intsubfield": int64(1020), + "floatsubfield": float64(1.020), + "boolfield": true, + }, + map[string]interface{}{ + "stringsubfield": "idx1021", + "intsubfield": int64(1021), + "floatsubfield": float64(1.021), + "boolfield": false, + }, + }, + }, + map[string]interface{}{ + "field1": []interface{}{ + map[string]interface{}{ + "stringsubfield": "idx1110", + "intsubfield": int64(1110), + "floatsubfield": float64(1.110), + "boolfield": true, + }, + map[string]interface{}{ + "stringsubfield": "idx1111", + "intsubfield": int64(1111), + "floatsubfield": float64(1.111), + "boolfield": false, + }, + }, + "field2": []interface{}{ + map[string]interface{}{ + "stringsubfield": "idx1120", + "intsubfield": int64(1120), + "floatsubfield": float64(1.1120), + "boolfield": true, + }, + map[string]interface{}{ + "stringsubfield": "idx1121", + "intsubfield": int64(1121), + "floatsubfield": float64(1.1121), + "boolfield": false, + }, + }, + }, + }, + } +} + +func TestBasicYamlOperationFromMap(t *testing.T) { + bytes, err := yaml.Marshal(makeBigMap()) + if err != nil { + t.Fatalf("unexpected yaml.Marshal err: %v", err) + } + if string(bytes) != bigMapYaml { + t.Fatalf("unexpected string equality") + } + rNode, err := kyaml.Parse(string(bytes)) + if err != nil { + t.Fatalf("unexpected yaml.Marshal err: %v", err) + } + rNodeString := rNode.MustString() + // The result from MustString has more indentation + // than bigMapYaml. + rNodeStrings := strings.Split(rNodeString, "\n") + bigMapStrings := strings.Split(bigMapYaml, "\n") + if len(rNodeStrings) != len(bigMapStrings) { + t.Fatalf("line count mismatch") + } + for i := range rNodeStrings { + s1 := strings.TrimSpace(rNodeStrings[i]) + s2 := strings.TrimSpace(bigMapStrings[i]) + if s1 != s2 { + t.Fatalf("expected '%s'=='%s'", s1, s2) + } + } +} + +func TestRoundTripJSON(t *testing.T) { + wn := NewWNode() + err := wn.UnmarshalJSON([]byte(deploymentLittleJson)) + if err != nil { + t.Fatalf("unexpected UnmarshalJSON err: %v", err) + } + data, err := wn.MarshalJSON() + if err != nil { + t.Fatalf("unexpected MarshalJSON err: %v", err) + } + actual := string(data) + if actual != deploymentLittleJson { + t.Fatalf("expected %s, got %s", deploymentLittleJson, actual) + } +} + +func TestGettingFields(t *testing.T) { + wn := NewWNode() + err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)) + if err != nil { + t.Fatalf("unexpected unmarshaljson err: %v", err) + } + gvk := wn.GetGvk() + expected := "apps" + actual := gvk.Group + if expected != actual { + t.Fatalf("expected '%s', got '%s'", expected, actual) + } + expected = "v1" + actual = gvk.Version + if expected != actual { + t.Fatalf("expected '%s', got '%s'", expected, actual) + } + expected = "Deployment" + actual = gvk.Kind + if expected != actual { + t.Fatalf("expected '%s', got '%s'", expected, actual) + } + actual = wn.GetKind() + if expected != actual { + t.Fatalf("expected '%s', got '%s'", expected, actual) + } + expected = "homer" + actual = wn.GetName() + if expected != actual { + t.Fatalf("expected '%s', got '%s'", expected, actual) + } + actualMap := wn.GetLabels() + v, ok := actualMap["fruit"] + if !ok || v != "apple" { + t.Fatalf("unexpected labels '%v'", actualMap) + } + actualMap = wn.GetAnnotations() + v, ok = actualMap["greeting"] + if !ok || v != "Take me to your leader." { + t.Fatalf("unexpected annotations '%v'", actualMap) + } +} diff --git a/api/k8sdeps/validator/kustvalidator.go b/api/k8sdeps/validator/kustvalidator.go index 7ac71ecead..65147320db 100644 --- a/api/k8sdeps/validator/kustvalidator.go +++ b/api/k8sdeps/validator/kustvalidator.go @@ -14,11 +14,14 @@ import ( v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/kustomize/api/ifc" ) // KustValidator validates Labels and annotations by apimachinery type KustValidator struct{} +var _ ifc.Validator = (*KustValidator)(nil) + // NewKustValidator returns a KustValidator object func NewKustValidator() *KustValidator { return &KustValidator{} diff --git a/api/krusty/internal/provider/depprovider.go b/api/krusty/internal/provider/depprovider.go new file mode 100644 index 0000000000..242cfdc212 --- /dev/null +++ b/api/krusty/internal/provider/depprovider.go @@ -0,0 +1,183 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package provider + +import ( + "sigs.k8s.io/kustomize/api/ifc" + "sigs.k8s.io/kustomize/api/internal/k8sdeps/merge" + kmerge "sigs.k8s.io/kustomize/api/internal/merge" + "sigs.k8s.io/kustomize/api/internal/validate" + "sigs.k8s.io/kustomize/api/internal/wrappy" + "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" + "sigs.k8s.io/kustomize/api/k8sdeps/validator" + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/resource" +) + +// DepProvider is a dependency provider. +// +// The instances it returns are either +// - old implementations backed by k8sdeps code, +// - new implementations backed by kyaml code. +// +// History: +// +// kubectl depends on k8s.io code, and at the time of writing, so +// does kustomize. Code that imports k8s.io/api* cannot be imported +// back into k8s.io/*, yet kustomize appears inside k8s.io/kubectl. +// +// To allow kustomize to appear inside kubectl, yet still be developed +// outside kubectl, the kustomize code was divided into the following +// packages +// +// api/ +// k8sdeps/ (and internal/ks8deps/) +// ifc/ +// krusty/ +// everythingElse/ +// +// with the following rules: +// +// - Only k8sdeps/ may import k8s.io/api*. +// +// - Only krusty/ (and its internals) may import k8sdeps/. +// I.e., ifc/ and everythingElse/ must not +// import k8sdeps/ or k8s.io/api*. +// +// - Code in krusty/ may use code in k8sdeps/ to create +// objects then inject said objects into +// everythingElse/ behind dependency neutral interfaces. +// +// The idea was to periodically copy, not import, the large k8sdeps/ +// tree (plus a snippet from krusty/kustomizer.go) into the kubectl +// codebase via a large PR, and have kubectl depend on the rest via +// normal importing. +// +// Over 2019, however, kubectl underwent large changes including +// a switch to Go modules, and a concerted attempt to extract kubectl +// from the k8s repo. This made large kustomize integration PRs too +// intrusive to review. +// +// In 2020, kubectl is based on Go modules, and almost entirely +// extracted from the k8s.io repositories, and further the kyaml +// library has a appeared as a viable replacement to k8s.io/api* +// KRM manipulation code. +// +// The new plan is to eliminate k8sdeps/ entirely, along with its +// k8s.io/api* dependence, allowing kustomize code to be imported +// into kubectl via normal Go module imports. Then the kustomize API +// code can then move into the github.com/kubernetes-sigs/cli-utils +// repo. The kustomize CLI in github.com/kubernetes-sigs/kustomize +// and the kubectl CLI can then both depend on the kustomize API. +// +// So, all code that depends on k8sdeps must go behind interfaces, +// and kustomize must be factored to choose the implementation. +// +// That problem has been reduced to three interfaces, each having +// two implementations. (1) is k8sdeps-based, (2) is kyaml-based. +// +// - ifc.Kunstructured +// +// 1) api/k8sdeps/kunstruct.UnstructAdapter +// +// This adapts structs in +// k8s.io/apimachinery/pkg/apis/meta/v1/unstructured +// to ifc.Kunstructured. +// +// 2) api/wrappy.WNode +// +// This adapts sigs.k8s.io/kustomize/kyaml/yaml.RNode +// to ifc.Unstructured. +// +// At time of writing, implementation started. +// Further reducing the size of ifc.Kunstructed +// would really reduce the work +// (e.g. drop Vars, drop ReplacementTranformer). +// +// - resmap.Merginator +// +// 1) api/internal/k8sdeps/merge.Merginator +// +// Uses k8s.io/apimachinery/pkg/util/strategicpatch, +// apimachinery/pkg/util/mergepatch, etc. to merge +// resource.Resource instances. +// +// 2) api/internal/merge.Merginator +// +// At time of writing, this is unimplemented. +// +// - ifc.Validator +// +// 1) api/k8sdeps/validator.KustValidator +// +// Uses k8s.io/apimachinery/pkg/api/validation and +// friends to validate strings. +// +// 2) api/internal/validate.FieldValidator +// +// At time of writing, this is a do-nothing +// validator as it's not critical to kustomize function. +// +// Proposed plan: +// [ ] Ship kustomize with the ability to switch from 1 to 2 via +// an --enable_kyaml flag. +// [ ] Make --enable_kyaml true by default. +// [ ] When 2 is not noticeably more buggy than 1, delete 1. +// I.e. delete k8sdeps/, transitively deleting all k8s.io/api* deps. +// This DepProvider should be left in place to retain these +// comments, but it will have only one choice. +// [ ] The way is now clear to reintegrate into kubectl. +// This should be done ASAP; the last step is cleanup. +// [ ] With only one impl of Kunstructure remaining, that interface +// and WNode can be deleted, along with this DepProvider. +// The other two interfaces could be dropped too. +// +// When the above is done, kustomize will use yaml.RNode and/or +// KRM Config Functions directly and exclusively. +// If you're reading this, plan not done. +// +type DepProvider struct { + resourceFactory *resource.Factory + merginator resmap.Merginator + fieldValidator ifc.Validator +} + +func makeK8sdepBasedInstances() *DepProvider { + kf := kunstruct.NewKunstructuredFactoryImpl() + rf := resource.NewFactory(kf) + return &DepProvider{ + resourceFactory: rf, + merginator: merge.NewMerginator(rf), + fieldValidator: validator.NewKustValidator(), + } +} + +func makeKyamlBasedInstances() *DepProvider { + kf := &wrappy.WNodeFactory{} + rf := resource.NewFactory(kf) + return &DepProvider{ + resourceFactory: rf, + merginator: kmerge.NewMerginator(rf), + fieldValidator: validate.NewFieldValidator(), + } +} + +func NewDepProvider(useKyaml bool) *DepProvider { + if useKyaml { + return makeKyamlBasedInstances() + } + return makeK8sdepBasedInstances() +} + +func (dp *DepProvider) GetResourceFactory() *resource.Factory { + return dp.resourceFactory +} + +func (dp *DepProvider) GetMerginator() resmap.Merginator { + return dp.merginator +} + +func (dp *DepProvider) GetFieldValidator() ifc.Validator { + return dp.fieldValidator +} diff --git a/api/krusty/kustomizer.go b/api/krusty/kustomizer.go index 07fbc9145f..9dad1beb9f 100644 --- a/api/krusty/kustomizer.go +++ b/api/krusty/kustomizer.go @@ -8,16 +8,13 @@ import ( "sigs.k8s.io/kustomize/api/builtins" "sigs.k8s.io/kustomize/api/filesys" - "sigs.k8s.io/kustomize/api/internal/k8sdeps/merge" pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader" "sigs.k8s.io/kustomize/api/internal/target" - "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" - "sigs.k8s.io/kustomize/api/k8sdeps/validator" "sigs.k8s.io/kustomize/api/konfig" + "sigs.k8s.io/kustomize/api/krusty/internal/provider" fLdr "sigs.k8s.io/kustomize/api/loader" "sigs.k8s.io/kustomize/api/provenance" "sigs.k8s.io/kustomize/api/resmap" - "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/types" ) @@ -28,13 +25,18 @@ import ( // number of overlays and bases), then make a Kustomizer // injected with the given fileystem, then call Run. type Kustomizer struct { - fSys filesys.FileSystem - options *Options + fSys filesys.FileSystem + options *Options + depProvider *provider.DepProvider } // MakeKustomizer returns an instance of Kustomizer. func MakeKustomizer(fSys filesys.FileSystem, o *Options) *Kustomizer { - return &Kustomizer{fSys: fSys, options: o} + return &Kustomizer{ + fSys: fSys, + options: o, + depProvider: provider.NewDepProvider(o.UseKyaml), + } } // Run performs a kustomization. @@ -49,11 +51,9 @@ func MakeKustomizer(fSys filesys.FileSystem, o *Options) *Kustomizer { // on any number of internal paths (e.g. the filesystem may contain // multiple overlays, and Run can be called on each of them). func (b *Kustomizer) Run(path string) (resmap.ResMap, error) { - resourceFactory := resource.NewFactory( - kunstruct.NewKunstructuredFactoryImpl()) resmapFactory := resmap.NewFactory( - resourceFactory, - merge.NewMerginator(resourceFactory)) + b.depProvider.GetResourceFactory(), + b.depProvider.GetMerginator()) lr := fLdr.RestrictionNone if b.options.LoadRestrictions == types.LoadRestrictionsRootOnly { lr = fLdr.RestrictionRootOnly @@ -65,7 +65,7 @@ func (b *Kustomizer) Run(path string) (resmap.ResMap, error) { defer ldr.Cleanup() kt := target.NewKustTarget( ldr, - validator.NewKustValidator(), + b.depProvider.GetFieldValidator(), resmapFactory, pLdr.NewLoader(b.options.PluginConfig, resmapFactory), ) diff --git a/api/krusty/options.go b/api/krusty/options.go index 44d61fc87e..041ca729d6 100644 --- a/api/krusty/options.go +++ b/api/krusty/options.go @@ -32,6 +32,10 @@ type Options struct { // Options related to kustomize plugins. PluginConfig *types.PluginConfig + + // When true, use kyaml/ packages to manipulate KRM yaml. + // When false, use k8sdeps/ instead (uses k8s.io/api* packages). + UseKyaml bool } // MakeDefaultOptions returns a default instance of Options. @@ -42,5 +46,6 @@ func MakeDefaultOptions() *Options { LoadRestrictions: types.LoadRestrictionsRootOnly, DoPrune: false, PluginConfig: konfig.DisabledPluginConfig(), + UseKyaml: false, } } diff --git a/kustomize/internal/commands/build/build.go b/kustomize/internal/commands/build/build.go index 7f9f323dbd..55da661cbd 100644 --- a/kustomize/internal/commands/build/build.go +++ b/kustomize/internal/commands/build/build.go @@ -95,6 +95,7 @@ func NewCmdBuild(out io.Writer) *cobra.Command { addFlagEnablePlugins(cmd.Flags()) addFlagReorderOutput(cmd.Flags()) addFlagEnableManagedbyLabel(cmd.Flags()) + addFlagEnableKyaml(cmd.Flags()) return cmd } @@ -132,6 +133,7 @@ func (o *Options) makeOptions() *krusty.Options { opts.PluginConfig = c } opts.AddManagedbyLabel = isManagedbyLabelEnabled() + opts.UseKyaml = flagEnableKyamlValue return opts } diff --git a/kustomize/internal/commands/build/flagEnableKyaml.go b/kustomize/internal/commands/build/flagEnableKyaml.go new file mode 100644 index 0000000000..c73f81dc25 --- /dev/null +++ b/kustomize/internal/commands/build/flagEnableKyaml.go @@ -0,0 +1,21 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "github.com/spf13/pflag" +) + +var ( + flagEnableKyamlValue = false +) + +func addFlagEnableKyaml(set *pflag.FlagSet) { + set.BoolVar( + &flagEnableKyamlValue, + "enable_kyaml", // flag name + false, // default value + "enable dependence on kyaml instead of k8sdeps.", // help + ) +}