diff --git a/mockkubeapiserver/apiserver.go b/mockkubeapiserver/apiserver.go index 78f96bd9..f78bcae4 100644 --- a/mockkubeapiserver/apiserver.go +++ b/mockkubeapiserver/apiserver.go @@ -51,7 +51,7 @@ func NewMockKubeAPIServer(addr string, options ...Option) (*MockKubeAPIServer, e } // These hooks mock behaviour that would otherwise require full controllers - s.storage.AddStorageHook(&hooks.CRDHook{}) + s.storage.AddStorageHook(&hooks.CRDHook{Storage: s.storage}) s.storage.AddStorageHook(&hooks.NamespaceHook{}) s.storage.AddStorageHook(&hooks.DeploymentHook{}) diff --git a/mockkubeapiserver/hooks/crd.go b/mockkubeapiserver/hooks/crd.go index 0bf48ea3..a928dd66 100644 --- a/mockkubeapiserver/hooks/crd.go +++ b/mockkubeapiserver/hooks/crd.go @@ -17,6 +17,8 @@ limitations under the License. package hooks import ( + "fmt" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/klog/v2" "sigs.k8s.io/kubebuilder-declarative-pattern/mockkubeapiserver/storage" @@ -24,15 +26,83 @@ import ( // CRDHook implements functionality for CRD objects (the definitions themselves, not instances of CRDs) type CRDHook struct { - storage storage.Storage + Storage storage.Storage } func (s *CRDHook) OnWatchEvent(ev *storage.WatchEvent) { switch ev.GroupKind() { case schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}: // When a CRD is created, we notify the storage layer so it can store instances of the CRD - if err := s.storage.UpdateCRD(ev); err != nil { + if err := s.Storage.UpdateCRD(ev); err != nil { klog.Warningf("crd change was invalid: %v", err) } + + if err := s.updateCRDConditions(ev); err != nil { + klog.Fatalf("could not update crd status: %v", err) + } + } +} +func (s *CRDHook) updateCRDConditions(ev *storage.WatchEvent) error { + u := ev.Unstructured() + + // So that CRDs become ready, we immediately update the status. + // We could do something better here, like e.g. a 1 second pause before changing the status + statusObj := u.Object["status"] + if statusObj == nil { + statusObj = make(map[string]interface{}) + u.Object["status"] = statusObj } + status, ok := statusObj.(map[string]interface{}) + if !ok { + return fmt.Errorf("status was of unexpected type %T", statusObj) + } + + generation := u.GetGeneration() + if generation == 0 { + generation = 1 + u.SetGeneration(generation) + } + + var conditions []interface{} + conditions = append(conditions, map[string]interface{}{ + "type": "NamesAccepted", + "status": "True", + "reason": "NoConflicts", + "message": "no conflicts found", + }) + conditions = append(conditions, map[string]interface{}{ + "type": "Established", + "status": "True", + "reason": "InitialNamesAccepted", + "message": "the initial names have been accepted", + }) + status["conditions"] = conditions + + // TODO: More of status? Here is an example of the full status + // status: + // acceptedNames: + // kind: VolumeSnapshot + // listKind: VolumeSnapshotList + // plural: volumesnapshots + // singular: volumesnapshot + // conditions: + // - lastTransitionTime: "2023-09-21T01:04:36Z" + // message: no conflicts found + // reason: NoConflicts + // status: "True" + // type: NamesAccepted + // - lastTransitionTime: "2023-09-21T01:04:36Z" + // message: the initial names have been accepted + // reason: InitialNamesAccepted + // status: "True" + // type: Established + // - lastTransitionTime: "2023-09-21T01:04:36Z" + // message: approved in https://github.com/kubernetes-csi/external-snapshotter/pull/419 + // reason: ApprovedAnnotation + // status: "True" + // type: KubernetesAPIApprovalPolicyConformant + // storedVersions: + // - v1 + + return nil } diff --git a/mockkubeapiserver/storage/memorystorage/crd.go b/mockkubeapiserver/storage/memorystorage/crd.go index 70ac7536..b99a2c3c 100644 --- a/mockkubeapiserver/storage/memorystorage/crd.go +++ b/mockkubeapiserver/storage/memorystorage/crd.go @@ -62,9 +62,10 @@ func (s *MemoryStorage) UpdateCRD(ev *storage.WatchEvent) error { gr := gvr.GroupResource() storage := &resourceStorage{ - GroupResource: gr, - objects: make(map[types.NamespacedName]*unstructured.Unstructured), - parent: s, + GroupResource: gr, + objects: make(map[types.NamespacedName]*unstructured.Unstructured), + parent: s, + resourceVersionClock: &s.resourceVersionClock, } // TODO: share storage across different versions