Skip to content

Commit 4ccc4a6

Browse files
committed
kustomize: load builtin schemas after reset
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
1 parent 59c882f commit 4ccc4a6

2 files changed

Lines changed: 154 additions & 4 deletions

File tree

kustomize/kustomize_generator.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,11 @@ func adaptSelector(selector *kustomize.Selector) (output *kustypes.Selector) {
630630
// buildMutex protects against kustomize concurrent map read/write panic
631631
var kustomizeBuildMutex sync.Mutex
632632

633+
func resetOpenAPIWithBuiltins() {
634+
openapi.ResetOpenAPI()
635+
_ = openapi.Schema()
636+
}
637+
633638
// Secure Build wraps krusty.MakeKustomizer with the following settings:
634639
// - secure on-disk FS denying operations outside root
635640
// - load files from outside the kustomization dir path
@@ -677,10 +682,11 @@ func Build(fs filesys.FileSystem, dirPath string) (res resmap.ResMap, err error)
677682
}
678683

679684
// Reset the global OpenAPI schema to ensure each build is isolated.
680-
// This prevents a custom openapi configuration in one Kustomization
681-
// from affecting subsequent builds (e.g., causing strategic-merge
682-
// patches to fail for types omitted from the custom schema).
683-
openapi.ResetOpenAPI()
685+
// Preload the embedded Kubernetes schema so openapi.path schemas add to
686+
// built-ins within the same build, matching the historical non-empty global
687+
// schema behavior without leaking schemas across builds.
688+
resetOpenAPIWithBuiltins()
689+
defer openapi.ResetOpenAPI()
684690

685691
k := krusty.MakeKustomizer(buildOptions)
686692
return k.Run(fs, dirPath)

kustomize/kustomize_generator_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ limitations under the License.
1717
package kustomize_test
1818

1919
import (
20+
"context"
2021
"fmt"
22+
"net/http"
23+
"net/http/httptest"
2124
"os"
2225
"path/filepath"
2326
"strings"
@@ -26,6 +29,7 @@ import (
2629
. "github.com/onsi/gomega"
2730
"github.com/otiai10/copy"
2831
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
2933
"sigs.k8s.io/kustomize/api/resmap"
3034
kustypes "sigs.k8s.io/kustomize/api/types"
3135
"sigs.k8s.io/kustomize/kyaml/filesys"
@@ -741,3 +745,143 @@ func TestOpenAPIPollution(t *testing.T) {
741745
g.Expect(string(yaml2)).To(ContainSubstring("memory: 64Mi"))
742746
})
743747
}
748+
749+
func TestOpenAPIPathMergesBuiltins(t *testing.T) {
750+
g := NewWithT(t)
751+
752+
tmpDir, err := testTempDir(t)
753+
g.Expect(err).ToNot(HaveOccurred())
754+
writeOpenAPIPathDaemonSetFixture(g, tmpDir, "custom-openapi.json")
755+
756+
res, err := kustomize.SecureBuild(tmpDir, tmpDir, false)
757+
g.Expect(err).ToNot(HaveOccurred())
758+
g.Expect(res.Resources()).To(HaveLen(1))
759+
760+
expectOpenAPIPathDaemonSet(g, res)
761+
}
762+
763+
func TestOpenAPIPathHTTPURLMergesBuiltins(t *testing.T) {
764+
g := NewWithT(t)
765+
766+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
767+
g.Expect(r.URL.Path).To(Equal("/custom-openapi.json"))
768+
w.Header().Set("Content-Type", "application/json")
769+
_, err := w.Write([]byte(customOpenAPISchema))
770+
g.Expect(err).ToNot(HaveOccurred())
771+
}))
772+
defer server.Close()
773+
774+
tmpDir, err := testTempDir(t)
775+
g.Expect(err).ToNot(HaveOccurred())
776+
writeOpenAPIPathDaemonSetFixture(g, tmpDir, server.URL+"/custom-openapi.json")
777+
778+
res, err := kustomize.SecureBuild(tmpDir, tmpDir, false)
779+
g.Expect(err).ToNot(HaveOccurred())
780+
g.Expect(res.Resources()).To(HaveLen(1))
781+
782+
expectOpenAPIPathDaemonSet(g, res)
783+
}
784+
785+
func expectOpenAPIPathDaemonSet(g Gomega, res resmap.ResMap) {
786+
out, err := res.AsYaml()
787+
g.Expect(err).ToNot(HaveOccurred())
788+
obj := daemonSetObject(g, out)
789+
container := daemonSetContainer(g, obj.Object)
790+
image, found, err := unstructured.NestedString(container, "image")
791+
g.Expect(err).ToNot(HaveOccurred())
792+
g.Expect(found).To(BeTrue())
793+
g.Expect(image).To(Equal("quay.io/prometheus/node-exporter:v1.10.2"))
794+
795+
volumeMounts, foundVolumeMounts, err := unstructured.NestedSlice(container, "volumeMounts")
796+
g.Expect(err).ToNot(HaveOccurred())
797+
g.Expect(foundVolumeMounts).To(BeTrue())
798+
g.Expect(volumeMounts).To(BeEmpty())
799+
800+
g.Expect(kubeClient.Create(context.Background(), obj, client.DryRunAll)).To(Succeed())
801+
}
802+
803+
func daemonSetObject(g Gomega, data []byte) *unstructured.Unstructured {
804+
var obj map[string]interface{}
805+
g.Expect(yaml.Unmarshal(data, &obj)).To(Succeed())
806+
u := &unstructured.Unstructured{Object: obj}
807+
u.SetNamespace("default")
808+
return u
809+
}
810+
811+
func daemonSetContainer(g Gomega, obj map[string]interface{}) map[string]interface{} {
812+
containers, found, err := unstructured.NestedSlice(obj, "spec", "template", "spec", "containers")
813+
g.Expect(err).ToNot(HaveOccurred())
814+
g.Expect(found).To(BeTrue())
815+
g.Expect(containers).To(HaveLen(1))
816+
container, ok := containers[0].(map[string]interface{})
817+
g.Expect(ok).To(BeTrue())
818+
return container
819+
}
820+
821+
func writeOpenAPIPathDaemonSetFixture(g Gomega, dir, openAPIPath string) {
822+
files := map[string]string{
823+
"kustomization.yaml": fmt.Sprintf(`resources:
824+
- daemonset.yaml
825+
openapi:
826+
path: %s
827+
patches:
828+
- patch: |-
829+
apiVersion: apps/v1
830+
kind: DaemonSet
831+
metadata:
832+
name: monitoring-prometheus-node-exporter
833+
spec:
834+
template:
835+
spec:
836+
containers:
837+
- name: node-exporter
838+
volumeMounts:
839+
- name: root
840+
mountPropagation: None
841+
target:
842+
group: apps
843+
kind: DaemonSet
844+
name: monitoring-prometheus-node-exporter
845+
`, openAPIPath),
846+
"custom-openapi.json": customOpenAPISchema,
847+
"daemonset.yaml": `apiVersion: apps/v1
848+
kind: DaemonSet
849+
metadata:
850+
name: monitoring-prometheus-node-exporter
851+
spec:
852+
selector:
853+
matchLabels:
854+
app: node-exporter
855+
template:
856+
metadata:
857+
labels:
858+
app: node-exporter
859+
spec:
860+
containers:
861+
- name: node-exporter
862+
image: quay.io/prometheus/node-exporter:v1.10.2
863+
volumeMounts:
864+
- name: root
865+
mountPath: /host/root
866+
volumes:
867+
- name: root
868+
hostPath:
869+
path: /
870+
`,
871+
}
872+
873+
for name, content := range files {
874+
g.Expect(os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)).To(Succeed())
875+
}
876+
}
877+
878+
const customOpenAPISchema = `{
879+
"swagger": "2.0",
880+
"info": {
881+
"title": "Custom schema",
882+
"version": "v1"
883+
},
884+
"paths": {},
885+
"definitions": {}
886+
}
887+
`

0 commit comments

Comments
 (0)