diff --git a/PROJECT b/PROJECT index 5752981f7..1fc7cdf9c 100644 --- a/PROJECT +++ b/PROJECT @@ -203,4 +203,12 @@ resources: kind: AwsNfsVolumeRestore path: github.com/kyma-project/cloud-manager/api/cloud-resources/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + domain: kyma-project.io + group: cloud-control + kind: SkrStatus + path: github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1 + version: v1beta1 version: "3" diff --git a/api/cloud-control/v1beta1/skrstatus_types.go b/api/cloud-control/v1beta1/skrstatus_types.go new file mode 100644 index 000000000..3e5963b13 --- /dev/null +++ b/api/cloud-control/v1beta1/skrstatus_types.go @@ -0,0 +1,93 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type SkrStatusCondition struct { + Title string `json:"title"` + ObjKindGroup string `json:"objKindGroup"` + CrdKindGroup string `json:"crdKindGroup"` + BusolaKindGroup string `json:"busolaKindGroup"` + Feature string `json:"feature"` + ObjName string `json:"objName"` + ObjNamespace string `json:"objNamespace"` + Filename string `json:"filename"` + Ok bool `json:"ok"` + Outcomes []string `json:"outcomes"` +} + +// SkrStatusSpec defines the desired state of SkrStatus. +type SkrStatusSpec struct { + KymaName string `json:"kymaName"` + Provider string `json:"provider"` + BrokerPlan string `json:"brokerPlan"` + GlobalAccount string `json:"globalAccount"` + SubAccount string `json:"subAccount"` + Region string `json:"region"` + ShootName string `json:"shootName"` + + PastConnections []metav1.Time `json:"pastConnections,omitempty"` + AverageIntervalSeconds int `json:"averageIntervalSeconds,omitempty"` + + Conditions []SkrStatusCondition `json:"conditions"` +} + +// SkrStatusStatus defines the observed state of SkrStatus. +type SkrStatusStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// SkrStatus is the Schema for the skrstatuses API. +type SkrStatus struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SkrStatusSpec `json:"spec,omitempty"` + Status SkrStatusStatus `json:"status,omitempty"` +} + +func (in *SkrStatus) CloneForPatch() *SkrStatus { + result := &SkrStatus{ + TypeMeta: in.TypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Namespace: in.Namespace, + Name: in.Name, + }, + Spec: in.Spec, + } + return result +} + +// +kubebuilder:object:root=true + +// SkrStatusList contains a list of SkrStatus. +type SkrStatusList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SkrStatus `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SkrStatus{}, &SkrStatusList{}) +} diff --git a/api/cloud-control/v1beta1/zz_generated.deepcopy.go b/api/cloud-control/v1beta1/zz_generated.deepcopy.go index 61fa0d115..3e3bb71f6 100644 --- a/api/cloud-control/v1beta1/zz_generated.deepcopy.go +++ b/api/cloud-control/v1beta1/zz_generated.deepcopy.go @@ -1565,6 +1565,129 @@ func (in *ScopeStatus) DeepCopy() *ScopeStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SkrStatus) DeepCopyInto(out *SkrStatus) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkrStatus. +func (in *SkrStatus) DeepCopy() *SkrStatus { + if in == nil { + return nil + } + out := new(SkrStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SkrStatus) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SkrStatusCondition) DeepCopyInto(out *SkrStatusCondition) { + *out = *in + if in.Outcomes != nil { + in, out := &in.Outcomes, &out.Outcomes + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkrStatusCondition. +func (in *SkrStatusCondition) DeepCopy() *SkrStatusCondition { + if in == nil { + return nil + } + out := new(SkrStatusCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SkrStatusList) DeepCopyInto(out *SkrStatusList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SkrStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkrStatusList. +func (in *SkrStatusList) DeepCopy() *SkrStatusList { + if in == nil { + return nil + } + out := new(SkrStatusList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SkrStatusList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SkrStatusSpec) DeepCopyInto(out *SkrStatusSpec) { + *out = *in + if in.PastConnections != nil { + in, out := &in.PastConnections, &out.PastConnections + *out = make([]v1.Time, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]SkrStatusCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkrStatusSpec. +func (in *SkrStatusSpec) DeepCopy() *SkrStatusSpec { + if in == nil { + return nil + } + out := new(SkrStatusSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SkrStatusStatus) DeepCopyInto(out *SkrStatusStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkrStatusStatus. +func (in *SkrStatusStatus) DeepCopy() *SkrStatusStatus { + if in == nil { + return nil + } + out := new(SkrStatusStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TimeOfDayGcp) DeepCopyInto(out *TimeOfDayGcp) { *out = *in diff --git a/config/crd/bases/cloud-control.kyma-project.io_skrstatuses.yaml b/config/crd/bases/cloud-control.kyma-project.io_skrstatuses.yaml new file mode 100644 index 000000000..16ac3fd2d --- /dev/null +++ b/config/crd/bases/cloud-control.kyma-project.io_skrstatuses.yaml @@ -0,0 +1,118 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.5 + name: skrstatuses.cloud-control.kyma-project.io +spec: + group: cloud-control.kyma-project.io + names: + kind: SkrStatus + listKind: SkrStatusList + plural: skrstatuses + singular: skrstatus + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: SkrStatus is the Schema for the skrstatuses API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: SkrStatusSpec defines the desired state of SkrStatus. + properties: + averageIntervalSeconds: + type: integer + brokerPlan: + type: string + conditions: + items: + properties: + busolaKindGroup: + type: string + crdKindGroup: + type: string + feature: + type: string + filename: + type: string + objKindGroup: + type: string + objName: + type: string + objNamespace: + type: string + ok: + type: boolean + outcomes: + items: + type: string + type: array + title: + type: string + required: + - busolaKindGroup + - crdKindGroup + - feature + - filename + - objKindGroup + - objName + - objNamespace + - ok + - outcomes + - title + type: object + type: array + globalAccount: + type: string + kymaName: + type: string + pastConnections: + items: + format: date-time + type: string + type: array + provider: + type: string + region: + type: string + shootName: + type: string + subAccount: + type: string + required: + - brokerPlan + - conditions + - globalAccount + - kymaName + - provider + - region + - shootName + - subAccount + type: object + status: + description: SkrStatusStatus defines the observed state of SkrStatus. + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index b769503db..70e83a89f 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -24,6 +24,7 @@ resources: - bases/cloud-resources.kyma-project.io_awsnfsbackupschedules.yaml - bases/cloud-control.kyma-project.io_nukes.yaml - bases/cloud-resources.kyma-project.io_awsnfsvolumerestores.yaml +- bases/cloud-control.kyma-project.io_skrstatuses.yaml #+kubebuilder:scaffold:crdkustomizeresource patches: diff --git a/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_skrstatuses.yaml b/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_skrstatuses.yaml new file mode 100644 index 000000000..16ac3fd2d --- /dev/null +++ b/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_skrstatuses.yaml @@ -0,0 +1,118 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.5 + name: skrstatuses.cloud-control.kyma-project.io +spec: + group: cloud-control.kyma-project.io + names: + kind: SkrStatus + listKind: SkrStatusList + plural: skrstatuses + singular: skrstatus + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: SkrStatus is the Schema for the skrstatuses API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: SkrStatusSpec defines the desired state of SkrStatus. + properties: + averageIntervalSeconds: + type: integer + brokerPlan: + type: string + conditions: + items: + properties: + busolaKindGroup: + type: string + crdKindGroup: + type: string + feature: + type: string + filename: + type: string + objKindGroup: + type: string + objName: + type: string + objNamespace: + type: string + ok: + type: boolean + outcomes: + items: + type: string + type: array + title: + type: string + required: + - busolaKindGroup + - crdKindGroup + - feature + - filename + - objKindGroup + - objName + - objNamespace + - ok + - outcomes + - title + type: object + type: array + globalAccount: + type: string + kymaName: + type: string + pastConnections: + items: + format: date-time + type: string + type: array + provider: + type: string + region: + type: string + shootName: + type: string + subAccount: + type: string + required: + - brokerPlan + - conditions + - globalAccount + - kymaName + - provider + - region + - shootName + - subAccount + type: object + status: + description: SkrStatusStatus defines the observed state of SkrStatus. + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/rbac/cloud-control_skrstatus_admin_role.yaml b/config/rbac/cloud-control_skrstatus_admin_role.yaml new file mode 100644 index 000000000..d502d825d --- /dev/null +++ b/config/rbac/cloud-control_skrstatus_admin_role.yaml @@ -0,0 +1,27 @@ +# This rule is not used by the project cloud-manager itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants full permissions ('*') over cloud-control.kyma-project.io. +# This role is intended for users authorized to modify roles and bindings within the cluster, +# enabling them to delegate specific permissions to other users or groups as needed. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: cloud-manager + app.kubernetes.io/managed-by: kustomize + name: cloud-control-skrstatus-admin-role +rules: +- apiGroups: + - cloud-control.kyma-project.io + resources: + - skrstatuses + verbs: + - '*' +- apiGroups: + - cloud-control.kyma-project.io + resources: + - skrstatuses/status + verbs: + - get diff --git a/config/rbac/cloud-control_skrstatus_editor_role.yaml b/config/rbac/cloud-control_skrstatus_editor_role.yaml new file mode 100644 index 000000000..ac0b294b2 --- /dev/null +++ b/config/rbac/cloud-control_skrstatus_editor_role.yaml @@ -0,0 +1,33 @@ +# This rule is not used by the project cloud-manager itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants permissions to create, update, and delete resources within the cloud-control.kyma-project.io. +# This role is intended for users who need to manage these resources +# but should not control RBAC or manage permissions for others. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: cloud-manager + app.kubernetes.io/managed-by: kustomize + name: cloud-control-skrstatus-editor-role +rules: +- apiGroups: + - cloud-control.kyma-project.io + resources: + - skrstatuses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - cloud-control.kyma-project.io + resources: + - skrstatuses/status + verbs: + - get diff --git a/config/rbac/cloud-control_skrstatus_viewer_role.yaml b/config/rbac/cloud-control_skrstatus_viewer_role.yaml new file mode 100644 index 000000000..ef6a3af76 --- /dev/null +++ b/config/rbac/cloud-control_skrstatus_viewer_role.yaml @@ -0,0 +1,29 @@ +# This rule is not used by the project cloud-manager itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants read-only access to cloud-control.kyma-project.io resources. +# This role is intended for users who need visibility into these resources +# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: cloud-manager + app.kubernetes.io/managed-by: kustomize + name: cloud-control-skrstatus-viewer-role +rules: +- apiGroups: + - cloud-control.kyma-project.io + resources: + - skrstatuses + verbs: + - get + - list + - watch +- apiGroups: + - cloud-control.kyma-project.io + resources: + - skrstatuses/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 61ac3d8ec..8a2231347 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -27,3 +27,10 @@ resources: - cloud-resources_cceenfsvolume_editor_role.yaml - cloud-resources_cceenfsvolume_viewer_role.yaml +# For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by +# default, aiding admins in cluster management. Those roles are +# not used by the {{ .ProjectName }} itself. You can comment the following lines +# if you do not want those helpers be installed with your Project. +- cloud-control_skrstatus_admin_role.yaml +- cloud-control_skrstatus_editor_role.yaml +- cloud-control_skrstatus_viewer_role.yaml \ No newline at end of file diff --git a/config/samples/cloud-control_v1beta1_skrstatus.yaml b/config/samples/cloud-control_v1beta1_skrstatus.yaml new file mode 100644 index 000000000..aa91b4f45 --- /dev/null +++ b/config/samples/cloud-control_v1beta1_skrstatus.yaml @@ -0,0 +1,9 @@ +apiVersion: cloud-control.kyma-project.io/v1beta1 +kind: SkrStatus +metadata: + labels: + app.kubernetes.io/name: cloud-manager + app.kubernetes.io/managed-by: kustomize + name: skrstatus-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 9142d3b93..ba0cf4143 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -22,4 +22,5 @@ resources: - cloud-resources_v1beta1_awsnfsbackupschedule.yaml - cloud-control_v1beta1_nuke.yaml - cloud-resources_v1beta1_awsnfsvolumerestore.yaml +- cloud-control_v1beta1_skrstatus.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/docs/contributor/ui/README.md b/docs/contributor/ui/README.md index a427a8300..b082f0db7 100644 --- a/docs/contributor/ui/README.md +++ b/docs/contributor/ui/README.md @@ -134,7 +134,7 @@ en: They are key-value pairs. -[Official Translations Documentation](https://github.com/kyma-project/busola/blob/main/docs/extensibility/translations-section.md) +[Official Translations Documentation](https://github.com/kyma-project/busola/blob/main/docs/extensibility/150-translations.md) # Generating the ConfigMap @@ -193,4 +193,4 @@ Official documentation and instructions can be found [here](https://kyma-project # Helpful Links -[Translations](https://github.com/kyma-project/busola/blob/main/docs/extensibility/translations-section.md) \ No newline at end of file +[Translations](https://github.com/kyma-project/busola/blob/main/docs/extensibility/150-translations.md) \ No newline at end of file diff --git a/pkg/feature/context.go b/pkg/feature/context.go index 5e4c03a59..ad7486acb 100644 --- a/pkg/feature/context.go +++ b/pkg/feature/context.go @@ -19,17 +19,89 @@ type contextKeyType struct{} var contextKey = contextKeyType{} var keyToBuilderMethod = map[types.Key]func(ContextBuilder, string) ContextBuilder{ - types.KeyFeature: ContextBuilder.Feature, - types.KeyPlane: ContextBuilder.Plane, - types.KeyProvider: ContextBuilder.Provider, - types.KeyBrokerPlan: ContextBuilder.BrokerPlan, - types.KeyGlobalAccount: ContextBuilder.GlobalAccount, - types.KeySubAccount: ContextBuilder.SubAccount, - types.KeyKyma: ContextBuilder.Kyma, - types.KeyShoot: ContextBuilder.Shoot, - types.KeyObjKindGroup: ContextBuilder.ObjKindGroup, + types.KeyFeature: ContextBuilder.Feature, + types.KeyPlane: ContextBuilder.Plane, + types.KeyProvider: ContextBuilder.Provider, + types.KeyBrokerPlan: ContextBuilder.BrokerPlan, + types.KeyGlobalAccount: ContextBuilder.GlobalAccount, + types.KeySubAccount: ContextBuilder.SubAccount, + types.KeyRegion: ContextBuilder.Region, + types.KeyKyma: ContextBuilder.Kyma, + types.KeyShoot: ContextBuilder.Shoot, + types.KeyObjKindGroup: ContextBuilder.ObjKindGroup, + types.KeyCrdKindGroup: ContextBuilder.CrdKindGroup, + types.KeyBusolaKindGroup: ContextBuilder.BusolaKindGroup, } +// ContextReader =============================================================== + +func NewContextReaderFromFFCtx(ffCtx ffcontext.Context) *ContextReader { + return &ContextReader{ffCtx: ffCtx} +} + +func NewContextReaderFromCtx(ctx context.Context) *ContextReader { + ffCtx := MustContextFromCtx(ctx) + return NewContextReaderFromFFCtx(ffCtx) +} + +type ContextReader struct { + ffCtx ffcontext.Context +} + +func (r *ContextReader) Landscape() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyLandscape]) +} + +func (r *ContextReader) Plane() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyPlane]) +} + +func (r *ContextReader) Feature() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyFeature]) +} + +func (r *ContextReader) Provider() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyProvider]) +} + +func (r *ContextReader) BrokerPlan() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyBrokerPlan]) +} + +func (r *ContextReader) GlobalAccount() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyGlobalAccount]) +} + +func (r *ContextReader) SubAccount() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeySubAccount]) +} + +func (r *ContextReader) Region() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyRegion]) +} + +func (r *ContextReader) Kyma() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyKyma]) +} + +func (r *ContextReader) Shoot() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyShoot]) +} + +func (r *ContextReader) ObjKindGroup() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyObjKindGroup]) +} + +func (r *ContextReader) CrdKindGroup() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyCrdKindGroup]) +} + +func (r *ContextReader) BusolaKindGroup() string { + return util.CastInterfaceToString(r.ffCtx.GetCustom()[types.KeyBusolaKindGroup]) +} + +// ContextBuilder =============================================================== + type ContextBuilder interface { Build(ctx context.Context) context.Context FFCtx() ffcontext.Context diff --git a/pkg/skr/runtime/alias.go b/pkg/skr/runtime/alias.go index 78ec03f16..be9ded07d 100644 --- a/pkg/skr/runtime/alias.go +++ b/pkg/skr/runtime/alias.go @@ -18,6 +18,7 @@ type ReconcilerFactory = reconcile.ReconcilerFactory type ReconcilerArguments = reconcile.ReconcilerArguments var NewRunner = looper.NewSkrRunner +var NewSkrRunnerWithNoopStatusSaver = looper.NewSkrRunnerWithNoopStatusSaver type SkrRegistry = registry.SkrRegistry diff --git a/pkg/skr/runtime/looper/installer.go b/pkg/skr/runtime/looper/installer.go index 424aa0a4f..0d614b599 100644 --- a/pkg/skr/runtime/looper/installer.go +++ b/pkg/skr/runtime/looper/installer.go @@ -28,6 +28,7 @@ type Installer interface { var _ Installer = &installer{} type installer struct { + skrStatus *SkrStatus skrProvidersPath string scheme *runtime.Scheme logger logr.Logger @@ -40,7 +41,7 @@ func (i *installer) Handle(ctx context.Context, provider string, skrCluster clus dir := path.Join(i.skrProvidersPath, provider) _, err := os.Stat(dir) if os.IsNotExist(err) { - return nil + return fmt.Errorf("installer directory %s does not exist: %w", dir, err) } if err != nil { return fmt.Errorf("error checking provider dir %s: %w", dir, err) @@ -114,15 +115,20 @@ func (i *installer) applyFile(ctx context.Context, skrCluster cluster.Cluster, f FeatureFromObject(desired, skrCluster.GetScheme()). Build(ctx) + handle := i.skrStatus.Handle(objCtx, "InstallerManfest") + handle.WithObj(desired) + handle.WithFilename(filepath.Base(fn)) + logger := feature.DecorateLogger(objCtx, i.logger). WithValues( - "manifestName", desired.GetName(), - "manifestNamespace", desired.GetNamespace(), + "objName", desired.GetName(), + "objNamespace", desired.GetNamespace(), "manifestFile", filepath.Base(fn), ) if !common.ObjSupportsProvider(desired, i.scheme, provider) { - logger.Info("Object Kind does not support this provider") + handle.NotSupportedByProvider() + //logger.Info("Object Kind does not support this provider") continue } @@ -140,29 +146,38 @@ func (i *installer) applyFile(ctx context.Context, skrCluster cluster.Cluster, f desiredVersion := i.getVersion(desired) existingVersion := i.getVersion(existing) if desiredVersion == existingVersion { + handle.AlreadyExistsWithSameVersion(desiredVersion) continue } err = i.copyForUpdate(desired, existing) if err != nil { + handle.SpecCopyError(err) + // leave this log since it indicates a developer logical error that happens rarely and only if copy spec code is invalid logger.Error(err, fmt.Sprintf("Error copying spec for %s/%s/%s before update", desired.GetAPIVersion(), desired.GetKind(), desired.GetName())) continue } - logger.Info(fmt.Sprintf("Updating %s/%s/%s from version %s to %s", desired.GetAPIVersion(), desired.GetKind(), desired.GetName(), existingVersion, desiredVersion)) + handle.Updating(existingVersion, desiredVersion) + //logger.Info(fmt.Sprintf("Updating %s/%s/%s from version %s to %s", desired.GetAPIVersion(), desired.GetKind(), desired.GetName(), existingVersion, desiredVersion)) err = skrCluster.GetClient().Update(ctx, existing) } else { err = nil // clear the not found error, so we only return Create error if any, and not this not found if feature.ApiDisabled.Value(objCtx) { - logger.Info(fmt.Sprintf("Skipping installation of disabled API of %s/%s/%s", desired.GetAPIVersion(), desired.GetKind(), desired.GetName())) + handle.ApiDisabled() + //logger.Info(fmt.Sprintf("Skipping installation of disabled API of %s/%s/%s", desired.GetAPIVersion(), desired.GetKind(), desired.GetName())) } else { - logger.Info(fmt.Sprintf("Creating %s/%s/%s", desired.GetAPIVersion(), desired.GetKind(), desired.GetName())) + handle.Creating() + //logger.Info(fmt.Sprintf("Creating %s/%s/%s", desired.GetAPIVersion(), desired.GetKind(), desired.GetName())) err = skrCluster.GetClient().Create(ctx, desired) } } if err != nil { + handle.Error(err) return docCount - 1, fmt.Errorf("error applying %s/%s/%s: %w", desired.GetAPIVersion(), desired.GetKind(), desired.GetName(), err) } + + handle.Success() } return docCount, nil diff --git a/pkg/skr/runtime/looper/runner.go b/pkg/skr/runtime/looper/runner.go index 1b3f2b7d0..e81e67c48 100644 --- a/pkg/skr/runtime/looper/runner.go +++ b/pkg/skr/runtime/looper/runner.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/go-logr/logr" cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" "github.com/kyma-project/cloud-manager/pkg/common" "github.com/kyma-project/cloud-manager/pkg/feature" @@ -51,19 +52,25 @@ type SkrRunner interface { Run(ctx context.Context, skrManager skrmanager.SkrManager, opts ...RunOption) error } -func NewSkrRunner(reg registry.SkrRegistry, kcpCluster cluster.Cluster) SkrRunner { +func NewSkrRunnerWithNoopStatusSaver(reg registry.SkrRegistry, kcpCluster cluster.Cluster) SkrRunner { + return NewSkrRunner(reg, kcpCluster, NewNoopStatusSaver()) +} + +func NewSkrRunner(reg registry.SkrRegistry, kcpCluster cluster.Cluster, skrStatusSaver SkrStatusSaver) SkrRunner { return &skrRunner{ - kcpCluster: kcpCluster, - registry: reg, + kcpCluster: kcpCluster, + registry: reg, + skrStatusSaver: skrStatusSaver, } } type skrRunner struct { - kcpCluster cluster.Cluster - registry registry.SkrRegistry - runOnce sync.Once - started bool - stopped bool + kcpCluster cluster.Cluster + registry registry.SkrRegistry + runOnce sync.Once + started bool + stopped bool + skrStatusSaver SkrStatusSaver } func (r *skrRunner) isObjectActiveForProvider(scheme *runtime.Scheme, provider *cloudcontrolv1beta1.ProviderType, obj client.Object) bool { @@ -79,28 +86,35 @@ func (r *skrRunner) Run(ctx context.Context, skrManager skrmanager.SkrManager, o } logger := skrManager.GetLogger() logger = feature.DecorateLogger(ctx, logger) - logger.Info("SKR Runner running") + //logger.Info("SKR Runner running") options := &RunOptions{} for _, o := range opts { o(options) } + r.runOnce.Do(func() { r.started = true + + skrStatus := NewSkrStatus(ctx) + defer func() { r.stopped = true + r.saveSkrStatus(ctx, skrStatus, logger) }() if options.checkSkrReadiness { chkr := &checker{logger: logger} if !chkr.IsReady(ctx, skrManager) { logger.Info("SKR cluster is not ready") + skrStatus.NotReady() return } } if options.provider != nil { - logger.Info(fmt.Sprintf("This SKR cluster is started with provider option %s", ptr.Deref(options.provider, ""))) + //logger.Info(fmt.Sprintf("This SKR cluster is started with provider option %s", ptr.Deref(options.provider, ""))) instlr := &installer{ + skrStatus: skrStatus, skrProvidersPath: config.SkrRuntimeConfig.ProvidersDir, scheme: skrManager.GetScheme(), logger: logger, @@ -128,30 +142,36 @@ func (r *skrRunner) Run(ctx context.Context, skrManager skrmanager.SkrManager, o KindsFromObject(indexer.Obj(), skrManager.GetScheme()). FeatureFromObject(indexer.Obj(), skrManager.GetScheme()). Build(ctx) - logger := feature.DecorateLogger(ctx, logger) + //logger := feature.DecorateLogger(ctx, logger) + + handle := skrStatus.Handle(ctx, "Indexer") + handle.WithObj(indexer.Obj()) if r.isObjectActiveForProvider(skrManager.GetScheme(), options.provider, indexer.Obj()) && !feature.ApiDisabled.Value(ctx) { + handle.Starting() err = indexer.IndexField(ctx, skrManager.GetFieldIndexer()) if err != nil { + handle.Error(err) err = fmt.Errorf("index filed error for %T: %w", indexer.Obj(), err) return } - logger. - WithValues( - "object", fmt.Sprintf("%T", indexer.Obj()), - "field", indexer.Field(), - "optionsProvider", ptr.Deref(options.provider, ""), - ). - Info("Starting indexer") + //logger. + // WithValues( + // "object", fmt.Sprintf("%T", indexer.Obj()), + // "field", indexer.Field(), + // "optionsProvider", ptr.Deref(options.provider, ""), + // ). + // Info("Starting indexer") } else { - logger. - WithValues( - "object", fmt.Sprintf("%T", indexer.Obj()), - "field", indexer.Field(), - "optionsProvider", ptr.Deref(options.provider, ""), - ). - Info("Not creating indexer due to disabled API") + handle.ApiDisabled() + //logger. + // WithValues( + // "object", fmt.Sprintf("%T", indexer.Obj()), + // "field", indexer.Field(), + // "optionsProvider", ptr.Deref(options.provider, ""), + // ). + // Info("Not creating indexer due to disabled API") } } @@ -161,31 +181,44 @@ func (r *skrRunner) Run(ctx context.Context, skrManager skrmanager.SkrManager, o KindsFromObject(b.GetForObj(), skrManager.GetScheme()). FeatureFromObject(b.GetForObj(), skrManager.GetScheme()). Build(ctx) - logger := feature.DecorateLogger(ctx, logger) + //logger := feature.DecorateLogger(ctx, logger) + + handle := skrStatus.Handle(ctx, "Controller") if r.isObjectActiveForProvider(skrManager.GetScheme(), options.provider, b.GetForObj()) && !feature.ApiDisabled.Value(ctx) { + handle.Starting() err = b.SetupWithManager(skrManager, rArgs) if err != nil { + handle.Error(err) err = fmt.Errorf("setup with manager error for %T: %w", b.GetForObj(), err) return } - logger. - WithValues( - "registryBuilderObject", fmt.Sprintf("%T", b.GetForObj()), - "optionsProvider", ptr.Deref(options.provider, ""), - ). - Info("Starting controller") + //logger. + // WithValues( + // "registryBuilderObject", fmt.Sprintf("%T", b.GetForObj()), + // "optionsProvider", ptr.Deref(options.provider, ""), + // ). + // Info("Starting controller") } else { - logger. - WithValues( - "registryBuilderObject", fmt.Sprintf("%T", b.GetForObj()), - "optionsProvider", ptr.Deref(options.provider, ""), - ). - Info("Not starting controller due to disabled API") + handle.ApiDisabled() + //logger. + // WithValues( + // "registryBuilderObject", fmt.Sprintf("%T", b.GetForObj()), + // "optionsProvider", ptr.Deref(options.provider, ""), + // ). + // Info("Not starting controller due to disabled API") } } + skrStatus.Connected() + + // this is a happy path saving, all other places are covered with the called form defer + // we want to save skrStatus here before manager is started and waited to timeout + // since it tracks its save status with IsSaved, after this call to save when defer is + // executed it will not save it again + r.saveSkrStatus(ctx, skrStatus, logger) + if options.timeout == 0 { options.timeout = time.Minute } @@ -203,3 +236,16 @@ func (r *skrRunner) Run(ctx context.Context, skrManager skrmanager.SkrManager, o }) return } + +func (r *skrRunner) saveSkrStatus(ctx context.Context, skrStatus *SkrStatus, logger logr.Logger) { + // save it only once + if skrStatus.IsSaved { + return + } + + err := r.skrStatusSaver.Save(ctx, skrStatus) + if err != nil { + logger.Error(err, "error saving SKR status") + } + skrStatus.IsSaved = true +} diff --git a/pkg/skr/runtime/looper/skrLooper.go b/pkg/skr/runtime/looper/skrLooper.go index 8aa8dc593..e36b57580 100644 --- a/pkg/skr/runtime/looper/skrLooper.go +++ b/pkg/skr/runtime/looper/skrLooper.go @@ -129,6 +129,7 @@ func New(kcpCluster cluster.Cluster, skrScheme *runtime.Scheme, reg registry.Skr activeSkrCollection: NewActiveSkrCollection(logger).(*activeSkrCollection), kcpCluster: kcpCluster, managerFactory: skrmanager.NewFactory(kcpCluster.GetAPIReader(), "kcp-system", skrScheme), + skrStatusSaver: NewSkrStatusSaver(NewSkrStatusRepo(kcpCluster.GetClient()), "kcp-system"), registry: reg, concurrency: config.SkrRuntimeConfig.Concurrency, } @@ -141,6 +142,7 @@ type skrLooper struct { managerFactory skrmanager.Factory registry registry.SkrRegistry concurrency int + skrStatusSaver SkrStatusSaver // wg the WorkGroup for workers wg sync.WaitGroup @@ -177,7 +179,7 @@ func (l *skrLooper) worker(id int) { logger.Info("SKR Looper worker started") for { shouldStop := func() bool { - logger.Info("SKR Looper worker about to read SKR ID") + //logger.Info("SKR Looper worker about to read SKR ID") item, shuttingDown := l.queue.Get() defer l.queue.Done(item) if shuttingDown { @@ -228,8 +230,8 @@ func (l *skrLooper) handleOneSkr(skrWorkerId int, kymaName string) { logger = feature.DecorateLogger(ctx, logger) - logger.Info("Starting SKR Runner") - runner := NewSkrRunner(l.registry, l.kcpCluster) + //logger.Info("Starting SKR Runner") + runner := NewSkrRunner(l.registry, l.kcpCluster, l.skrStatusSaver) to := 10 * time.Second if debugged.Debugged { to = 15 * time.Minute @@ -239,5 +241,5 @@ func (l *skrLooper) handleOneSkr(skrWorkerId int, kymaName string) { if err != nil { logger.Error(err, "Error running SKR Runner") } - logger.Info("SKR Runner stopped") + //logger.Info("SKR Runner stopped") } diff --git a/pkg/skr/runtime/looper/skrStatus.go b/pkg/skr/runtime/looper/skrStatus.go new file mode 100644 index 000000000..da0565520 --- /dev/null +++ b/pkg/skr/runtime/looper/skrStatus.go @@ -0,0 +1,283 @@ +package looper + +import ( + "context" + "fmt" + "github.com/elliotchance/pie/v2" + cloudcontrol1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" + "github.com/kyma-project/cloud-manager/pkg/common" + "github.com/kyma-project/cloud-manager/pkg/feature" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "math" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" +) + +// SkrStatusRepo ============================================================================ + +func NewSkrStatusRepo(kcpClient client.Client) SkrStatusRepo { + return &skrStatusRepo{ + kcpClient: kcpClient, + } +} + +type SkrStatusRepo interface { + Load(ctx context.Context, name, namespace string) (*cloudcontrol1beta1.SkrStatus, error) + Save(ctx context.Context, skrStatus *cloudcontrol1beta1.SkrStatus) error +} + +type skrStatusRepo struct { + kcpClient client.Client +} + +func (r *skrStatusRepo) Load(ctx context.Context, name, namespace string) (*cloudcontrol1beta1.SkrStatus, error) { + skrStatus := &cloudcontrol1beta1.SkrStatus{} + err := r.kcpClient.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, skrStatus) + if client.IgnoreNotFound(err) != nil { + return nil, fmt.Errorf("error loading SkrStatus %s/%s: %w", namespace, name, err) + } + if apierrors.IsNotFound(err) { + skrStatus.Name = name + skrStatus.Namespace = namespace + skrStatus.TypeMeta.Kind = "SkrStatus" + skrStatus.TypeMeta.APIVersion = cloudcontrol1beta1.GroupVersion.String() + return skrStatus, nil + } + return skrStatus, nil +} + +func (r *skrStatusRepo) Save(ctx context.Context, skrStatus *cloudcontrol1beta1.SkrStatus) error { + return r.kcpClient.Patch(ctx, skrStatus, client.Apply, client.ForceOwnership, client.FieldOwner(common.FieldOwner)) +} + +// SkrStatusSaver ========================================================================== + +func NewNoopStatusSaver() SkrStatusSaver { + return &noopSkrStatusSaver{} +} + +func NewSkrStatusSaver(repo SkrStatusRepo, namespace string) SkrStatusSaver { + return &skrStatusSaver{ + repo: repo, + namespace: namespace, + } +} + +type SkrStatusSaver interface { + Save(ctx context.Context, skrStatus *SkrStatus) error +} + +type noopSkrStatusSaver struct{} + +func (s *noopSkrStatusSaver) Save(ctx context.Context, skrStatus *SkrStatus) error { + return nil +} + +type skrStatusSaver struct { + repo SkrStatusRepo + namespace string +} + +func (s *skrStatusSaver) Save(ctx context.Context, skrStatus *SkrStatus) error { + api, err := s.repo.Load(ctx, skrStatus.kyma, s.namespace) + if err != nil { + return fmt.Errorf("error loading SkrStatus: %w", err) + } + + if api.Labels == nil { + api.Labels = map[string]string{} + } + if len(skrStatus.globalAccount) > 0 { + api.Labels[cloudcontrol1beta1.LabelScopeGlobalAccountId] = skrStatus.globalAccount + } + if len(skrStatus.subAccount) > 0 { + api.Labels[cloudcontrol1beta1.LabelScopeSubaccountId] = skrStatus.subAccount + } + if len(skrStatus.shoot) > 0 { + api.Labels[cloudcontrol1beta1.LabelScopeShootName] = skrStatus.shoot + } + if len(skrStatus.region) > 0 { + api.Labels[cloudcontrol1beta1.LabelScopeRegion] = skrStatus.region + } + if len(skrStatus.brokerPlan) > 0 { + api.Labels[cloudcontrol1beta1.LabelScopeBrokerPlanName] = skrStatus.brokerPlan + } + + api.Spec.KymaName = skrStatus.kyma + api.Spec.Provider = skrStatus.provider + api.Spec.BrokerPlan = skrStatus.brokerPlan + api.Spec.GlobalAccount = skrStatus.globalAccount + api.Spec.SubAccount = skrStatus.subAccount + api.Spec.Region = skrStatus.region + api.Spec.ShootName = skrStatus.shoot + + api.Spec.PastConnections = append(api.Spec.PastConnections, metav1.Now()) + if len(api.Spec.PastConnections) > 10 { + api.Spec.PastConnections = api.Spec.PastConnections[len(api.Spec.PastConnections)-10:] + } + + sum := float64(0) + count := 0 + last := (*time.Time)(nil) + for _, dt := range api.Spec.PastConnections { + if last != nil { + dur := dt.Sub(*last) + sum += dur.Seconds() + count++ + } + last = ptr.To(dt.Time) + } + api.Spec.AverageIntervalSeconds = 0 + if count > 0 { + api.Spec.AverageIntervalSeconds = int(math.Round(sum / float64(count))) + } + + api.Spec.Conditions = pie.Map(skrStatus.handles, func(x *KindHandle) cloudcontrol1beta1.SkrStatusCondition { + return cloudcontrol1beta1.SkrStatusCondition{ + Title: x.title, + ObjKindGroup: x.objKindGroup, + CrdKindGroup: x.crdKindGroup, + BusolaKindGroup: x.busolaKindGroup, + Feature: x.feature, + ObjName: x.objName, + ObjNamespace: x.objNamespace, + Filename: x.filename, + Ok: x.ok, + Outcomes: x.outcomes, + } + }) + + return s.repo.Save(ctx, api.CloneForPatch()) +} + +// SkrStatus ============================================================================= + +func NewSkrStatus(ctx context.Context) *SkrStatus { + reader := feature.NewContextReaderFromCtx(ctx) + return &SkrStatus{ + kyma: reader.Kyma(), + provider: reader.Provider(), + brokerPlan: reader.BrokerPlan(), + globalAccount: reader.GlobalAccount(), + subAccount: reader.SubAccount(), + region: reader.Region(), + shoot: reader.Shoot(), + + ok: false, + } +} + +type SkrStatus struct { + IsSaved bool + + kyma string + provider string + brokerPlan string + globalAccount string + subAccount string + region string + shoot string + + handles []*KindHandle + + ok bool + outcome string +} + +type KindHandle struct { + title string + objKindGroup string + crdKindGroup string + busolaKindGroup string + feature string + objName string + objNamespace string + filename string + ok bool + outcomes []string +} + +// SkrStatus ================================================================== + +// NotReady called when checker determines that SKR is not ready +func (s *SkrStatus) NotReady() { + s.outcome = "SkrIsNotReady" +} + +func (s *SkrStatus) Connected() { + s.outcome = "Connected" + s.ok = true +} + +// Handle called for each manifest found in the installation files. The outcome is recorded by called a method on the returned handle +func (s *SkrStatus) Handle(ctx context.Context, title string) *KindHandle { + reader := feature.NewContextReaderFromCtx(ctx) + h := &KindHandle{ + title: title, + objKindGroup: reader.ObjKindGroup(), + crdKindGroup: reader.CrdKindGroup(), + busolaKindGroup: reader.BusolaKindGroup(), + feature: reader.Feature(), + } + s.handles = append(s.handles, h) + return h +} + +// KindHandle ================================================= + +func (h *KindHandle) WithObj(obj client.Object) { + h.objName = obj.GetName() + h.objNamespace = obj.GetNamespace() +} + +func (h *KindHandle) WithFilename(filename string) { + h.filename = filename +} + +// NotSupportedByProvider called if manifest does not support the SKR provider +func (h *KindHandle) NotSupportedByProvider() { + h.outcomes = append(h.outcomes, "NotSupportedByProvider") +} + +// AlreadyExistsWithSameVersion called if manifest already exists in SKR with the version equal to desired in the installation file +func (h *KindHandle) AlreadyExistsWithSameVersion(v string) { + h.outcomes = append(h.outcomes, "AlreadyExistsWithSameVersion", fmt.Sprintf("Version: %s", v)) + h.ok = true +} + +// Updating called if manifest already exists in SKR but with different version then the one in the installation file +func (h *KindHandle) Updating(existingVersion string, desiredVersion string) { + h.outcomes = append(h.outcomes, "Updating", fmt.Sprintf("ExistingVersion: %s", existingVersion), fmt.Sprintf("DesiredVersion: %s", desiredVersion)) +} + +// ApiDisabled called if manifest does not exist in SKR but its feature is disabled +func (h *KindHandle) ApiDisabled() { + h.outcomes = append(h.outcomes, "ApiDisabled") +} + +// Creating called if manifest does not exist in SKR and its feature is enabled +func (h *KindHandle) Creating() { + h.outcomes = append(h.outcomes, "Creating") +} + +// Starting called if indexer or controller is started +func (h *KindHandle) Starting() { + h.outcomes = append(h.outcomes, "Starting") +} + +// Error called when creating, updating or starting got an error +func (h *KindHandle) Error(err error) { + h.outcomes = append(h.outcomes, fmt.Sprintf("Error: %v", err)) +} + +// SpecCopyError called if copy spec for update got an error. This is likely a developer logical error +// and means that the code must be changed to do copy properly. +func (h *KindHandle) SpecCopyError(err error) { + h.outcomes = append(h.outcomes, fmt.Sprintf("SpecCopyError: %v", err)) +} + +func (h *KindHandle) Success() { + h.ok = true +} diff --git a/pkg/skr/runtime/manager/manager.go b/pkg/skr/runtime/manager/manager.go index e58ef60e5..9b859c672 100644 --- a/pkg/skr/runtime/manager/manager.go +++ b/pkg/skr/runtime/manager/manager.go @@ -58,7 +58,7 @@ func (m *skrManager) KymaRef() klog.ObjectRef { } func (m *skrManager) Start(ctx context.Context) error { - m.logger.Info("SkrManager starting") + //m.logger.Info("SkrManager starting") m.controllers = append(m.controllers, m.Cluster) var wg sync.WaitGroup for _, r := range m.controllers { @@ -80,7 +80,7 @@ func (m *skrManager) Start(ctx context.Context) error { <-ctx.Done() wg.Wait() - m.logger.Info("SkrManager stopped") + //m.logger.Info("SkrManager stopped") return nil } diff --git a/pkg/testinfra/env.go b/pkg/testinfra/env.go index bfa45f32c..941b52ef0 100644 --- a/pkg/testinfra/env.go +++ b/pkg/testinfra/env.go @@ -99,7 +99,7 @@ func (ie *infraEnv) StartSkrControllers(ctx context.Context) { panic(fmt.Errorf("error creating SKR manager: %w", err)) } - ie.runner = skrruntime.NewRunner(ie.registry, newKcpClusterWrap(ie.kcpManager)) + ie.runner = skrruntime.NewSkrRunnerWithNoopStatusSaver(ie.registry, newKcpClusterWrap(ie.kcpManager)) ie.ctx, ie.cancel = context.WithCancel(ctx) go func() { defer ginkgo.GinkgoRecover()