@@ -15,6 +15,7 @@ import (
1515 "github.com/pkg/errors"
1616 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1717 apiextensionsv1clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
18+ apiequality "k8s.io/apimachinery/pkg/api/equality"
1819 kerrors "k8s.io/apimachinery/pkg/api/errors"
1920 "k8s.io/apimachinery/pkg/api/meta"
2021 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -23,6 +24,7 @@ import (
2324 "k8s.io/apimachinery/pkg/util/wait"
2425 "k8s.io/client-go/discovery"
2526 "k8s.io/client-go/rest"
27+ "k8s.io/client-go/util/retry"
2628 "k8s.io/klog/v2"
2729 "sigs.k8s.io/controller-runtime/pkg/client"
2830 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -376,44 +378,67 @@ func checkSubresourceStatus(ctx context.Context, vClient *apiextensionsv1clients
376378 return isClusterScoped , hasStatusSubresource , err
377379}
378380
379- func crdUpdateWithNewVersion (ctx context.Context , vClient * apiextensionsv1clientset.Clientset , pCrdDefinition , vCrdDefinition * apiextensionsv1.CustomResourceDefinition , groupVersionKind schema.GroupVersionKind ) (bool , bool , error ) {
380- var err error
381- isClusterScoped := vCrdDefinition .Spec .Scope == apiextensionsv1 .ClusterScoped
382- hasStatusSubresource := false
381+ func crdVersionsWithNewStorageVersion (crdVersions []apiextensionsv1.CustomResourceDefinitionVersion , storageVersion apiextensionsv1.CustomResourceDefinitionVersion ) []apiextensionsv1.CustomResourceDefinitionVersion {
382+ storageVersion .Storage = true
383383
384- // CRD exists but with different version. Need to add the new version to it, and set as storage version if it is not already set.
385- klog .FromContext (ctx ).Info ("CRD found in virtual cluster, checking versions" , "crd" , vCrdDefinition .Name , "groupVersionKind" , groupVersionKind )
386-
387- newVersions := []apiextensionsv1.CustomResourceDefinitionVersion {}
388- for _ , version := range vCrdDefinition .Spec .Versions {
389- if version .Name == groupVersionKind .Version {
384+ newVersions := make ([]apiextensionsv1.CustomResourceDefinitionVersion , 0 , len (crdVersions )+ 1 )
385+ for _ , version := range crdVersions {
386+ if version .Name == storageVersion .Name {
390387 continue
391388 }
392389 version .Storage = false
393390 newVersions = append (newVersions , version )
394391 }
395392
396- // Version not found, we need to add it
397- klog .FromContext (ctx ).Info ("CRD version not found in virtual cluster, adding it" , "version" , groupVersionKind .Version , "crd" , vCrdDefinition .Name )
393+ return append (newVersions , storageVersion )
394+ }
395+
396+ func crdUpdateWithNewVersion (ctx context.Context , vClient * apiextensionsv1clientset.Clientset , pCrdDefinition , vCrdDefinition * apiextensionsv1.CustomResourceDefinition , groupVersionKind schema.GroupVersionKind ) (bool , bool , error ) {
397+ // CRD exists but with different version. Need to add the new version to it, and set as storage version if it is not already set.
398+ isClusterScoped := vCrdDefinition .Spec .Scope == apiextensionsv1 .ClusterScoped
399+ hasStatusSubresource := false
400+
401+ klog .FromContext (ctx ).Info ("CRD found in virtual cluster, checking versions" , "crd" , vCrdDefinition .Name , "groupVersionKind" , groupVersionKind )
402+
398403 newVersion := getCrdVersionByName (pCrdDefinition .Spec .Versions , groupVersionKind .Version )
399404 if newVersion == nil {
400- err = fmt .Errorf ("could not find version %q in physical CRD %q" , groupVersionKind .Version , pCrdDefinition .Name )
401- return isClusterScoped , hasStatusSubresource , err
405+ return isClusterScoped , false , fmt .Errorf ("could not find version %q in physical CRD %q" , groupVersionKind .Version , pCrdDefinition .Name )
402406 }
403407 newVersion .Storage = true
404- newVersions = append (newVersions , * newVersion )
405- vCrdDefinition .Spec .Versions = newVersions
406- // Update the CRD in the virtual cluster
407- klog .FromContext (ctx ).Info ("Updating CRD in virtual cluster with new version" , "crd" , vCrdDefinition .Name , "version" , groupVersionKind .Version )
408- _ , err = vClient .ApiextensionsV1 ().CustomResourceDefinitions ().Update (ctx , vCrdDefinition , metav1.UpdateOptions {})
408+ hasStatusSubresource = hasStatus (* newVersion )
409+
410+ updated := false
411+ // In HA mode all replicas run this code concurrently (before leader election), so we use RetryOnConflict to handle potential
412+ // race occurring when multiple replicas try to update the CRD at the same time.
413+ err := retry .RetryOnConflict (retry .DefaultRetry , func () error {
414+ crdDefinition , err := vClient .ApiextensionsV1 ().CustomResourceDefinitions ().Get (ctx , vCrdDefinition .Name , metav1.GetOptions {})
415+ if err != nil {
416+ return err
417+ }
418+ isClusterScoped = crdDefinition .Spec .Scope == apiextensionsv1 .ClusterScoped
419+ newVersions := crdVersionsWithNewStorageVersion (crdDefinition .Spec .Versions , * newVersion )
420+ if apiequality .Semantic .DeepEqual (crdDefinition .Spec .Versions , newVersions ) {
421+ klog .FromContext (ctx ).Info ("CRD in virtual cluster already has desired version" , "crd" , crdDefinition .Name , "version" , groupVersionKind .Version )
422+ return nil
423+ }
424+ crdDefinition .Spec .Versions = newVersions
425+
426+ klog .FromContext (ctx ).Info ("Updating CRD in virtual cluster with new version" , "crd" , crdDefinition .Name , "version" , groupVersionKind .Version )
427+ _ , err = vClient .ApiextensionsV1 ().CustomResourceDefinitions ().Update (ctx , crdDefinition , metav1.UpdateOptions {})
428+ if kerrors .IsConflict (err ) {
429+ klog .FromContext (ctx ).Info ("CRD update conflict, retrying with latest ResourceVersion" , "crd" , crdDefinition .Name , "version" , groupVersionKind .Version )
430+ }
431+ updated = err == nil
432+ return err
433+ })
409434 if err != nil {
410- err = fmt .Errorf ("update crd in virtual cluster: %w" , err )
411- return isClusterScoped , hasStatusSubresource , err
435+ return isClusterScoped , false , fmt .Errorf ("update crd in virtual cluster: %w" , err )
412436 }
413- // Check if the status subresource is set
414- hasStatusSubresource = hasStatus (* newVersion )
415- klog .FromContext (ctx ).Info ("CRD updated in virtual cluster" , "crd" , vCrdDefinition .Name , "version" , groupVersionKind .Version , "hasStatusSubresource" , hasStatusSubresource )
416- return isClusterScoped , hasStatusSubresource , err
437+ if updated {
438+ // Check if the status subresource is set
439+ klog .FromContext (ctx ).Info ("CRD updated in virtual cluster" , "crd" , vCrdDefinition .Name , "version" , groupVersionKind .Version , "hasStatusSubresource" , hasStatusSubresource )
440+ }
441+ return isClusterScoped , hasStatusSubresource , nil
417442}
418443
419444func createCrdFromPhysicalCluster (ctx context.Context , vClient * apiextensionsv1clientset.Clientset , pCrdDefinition * apiextensionsv1.CustomResourceDefinition , groupVersionResource schema.GroupVersionResource , groupVersionKind schema.GroupVersionKind ) (bool , bool , error ) {
0 commit comments