diff --git a/kyaml/openapi/openapi.go b/kyaml/openapi/openapi.go index 69762775e3b..923cd414a0f 100644 --- a/kyaml/openapi/openapi.go +++ b/kyaml/openapi/openapi.go @@ -10,6 +10,7 @@ import ( "path/filepath" "reflect" "strings" + "sync" openapi_v2 "github.com/google/gnostic/openapiv2" "google.golang.org/protobuf/proto" @@ -24,9 +25,6 @@ import ( // globalSchema contains global state information about the openapi var globalSchema openapiData -// kubernetesOpenAPIVersion specifies which builtin kubernetes schema to use -var kubernetesOpenAPIVersion string - // customSchemaFile stores the custom OpenApi schema if it is provided var customSchema []byte @@ -59,6 +57,20 @@ const ( Proto format = "proto" ) +// kubernetesOpenAPIVersionLock lock for kubernetesOpenAPIVersion. +// +// NOTE: This lock helps with preventing panics that might occur due to the data +// race that concurrent access on this variable might cause but it doesn't +// fully fix the issue described in https://github.com/kubernetes-sigs/kustomize/issues/4824. +// For instance concurrently running goroutines where each of them calls SetSchema() +// and/or GetSchemaVersion might end up received nil errors (success) whereas the +// seconds one would overwrite the global variable that has been written by the +// first one. +var kubernetesOpenAPIVersionLock sync.RWMutex + +// kubernetesOpenAPIVersion specifies which builtin kubernetes schema to use. +var kubernetesOpenAPIVersion string + // precomputedIsNamespaceScoped precomputes IsNamespaceScoped for known types. This avoids Schema creation, // which is expensive // The test output from TestIsNamespaceScopedPrecompute shows the expected map in go syntax,and can be copy and pasted @@ -279,8 +291,11 @@ func AddSchema(s []byte) error { // ResetOpenAPI resets the openapi data to empty func ResetOpenAPI() { globalSchema = openapiData{} - kubernetesOpenAPIVersion = "" customSchema = nil + + kubernetesOpenAPIVersionLock.Lock() + defer kubernetesOpenAPIVersionLock.Unlock() + kubernetesOpenAPIVersion = "" } // AddDefinitions adds the definitions to the global schema. @@ -551,6 +566,9 @@ const ( // SetSchema sets the kubernetes OpenAPI schema version to use func SetSchema(openAPIField map[string]string, schema []byte, reset bool) error { + kubernetesOpenAPIVersionLock.Lock() + defer kubernetesOpenAPIVersionLock.Unlock() + // this should only be set once schemaIsSet := (kubernetesOpenAPIVersion != "") || customSchema != nil if schemaIsSet && !reset { @@ -588,6 +606,9 @@ func SetSchema(openAPIField map[string]string, schema []byte, reset bool) error // GetSchemaVersion returns what kubernetes OpenAPI version is being used func GetSchemaVersion() string { + kubernetesOpenAPIVersionLock.RLock() + defer kubernetesOpenAPIVersionLock.RUnlock() + switch { case kubernetesOpenAPIVersion == "" && customSchema == nil: return kubernetesOpenAPIDefaultVersion @@ -612,6 +633,8 @@ func initSchema() { panic("invalid schema file") } } else { + kubernetesOpenAPIVersionLock.RLock() + defer kubernetesOpenAPIVersionLock.RUnlock() if kubernetesOpenAPIVersion == "" { parseBuiltinSchema(kubernetesOpenAPIDefaultVersion) } else {