From 28edd6cc266246e99c780b7dea2ccb1f9b03ab5a Mon Sep 17 00:00:00 2001 From: "Badr, Nesma" Date: Thu, 26 Sep 2024 11:32:30 +0200 Subject: [PATCH 1/3] Change managedBy label to kyma instead of lifecycle-manager or declarative-v2 --- api/shared/operator_labels.go | 3 ++- api/v1beta2/kyma_types.go | 2 +- internal/declarative/v2/default_transforms.go | 3 +-- internal/declarative/v2/default_transforms_test.go | 2 +- internal/remote/skr_context.go | 2 +- pkg/module/common/module.go | 2 +- pkg/module/common/module_test.go | 2 +- pkg/testutils/builder/watcher.go | 12 ++++-------- pkg/testutils/utils.go | 2 +- pkg/watch/mandatory_template_change.go | 2 +- pkg/watcher/certificate_manager.go | 2 +- .../mandatorymodule/deletion/controller_test.go | 5 +++-- .../mandatorymodule/installation/controller_test.go | 2 +- .../controller/withwatcher/controller_test.go | 2 +- .../withwatcher/watcher_controller_helper_test.go | 6 +++--- 15 files changed, 23 insertions(+), 26 deletions(-) diff --git a/api/shared/operator_labels.go b/api/shared/operator_labels.go index dafe9d16e7..718a286f02 100644 --- a/api/shared/operator_labels.go +++ b/api/shared/operator_labels.go @@ -8,7 +8,8 @@ const ( ControllerName = OperatorGroup + Separator + "controller-name" ChannelLabel = OperatorGroup + Separator + "channel" // ManagedBy defines the controller managing the resource. - ManagedBy = OperatorGroup + Separator + "managed-by" + ManagedBy = OperatorGroup + Separator + "managed-by" + KymaLabelValue = "kyma" IstioInjectionLabel = "istio-injection" WardenLabel = "namespaces.warden.kyma-project.io/validate" diff --git a/api/v1beta2/kyma_types.go b/api/v1beta2/kyma_types.go index ef50f8e2f8..677b195c23 100644 --- a/api/v1beta2/kyma_types.go +++ b/api/v1beta2/kyma_types.go @@ -435,7 +435,7 @@ func (kyma *Kyma) EnsureLabelsAndFinalizers() bool { } if _, ok := kyma.ObjectMeta.Labels[shared.ManagedBy]; !ok { - kyma.ObjectMeta.Labels[shared.ManagedBy] = shared.OperatorName + kyma.ObjectMeta.Labels[shared.ManagedBy] = shared.KymaLabelValue updateRequired = true } return updateRequired diff --git a/internal/declarative/v2/default_transforms.go b/internal/declarative/v2/default_transforms.go index a64f660d8b..4ae628c4dc 100644 --- a/internal/declarative/v2/default_transforms.go +++ b/internal/declarative/v2/default_transforms.go @@ -11,7 +11,6 @@ import ( const ( OperatorName = "module-manager" - ManagedByLabelValue = "declarative-v2" DisclaimerAnnotation = shared.OperatorGroup + shared.Separator + "managed-by-reconciler-disclaimer" DisclaimerAnnotationValue = "DO NOT EDIT - This resource is managed by Kyma.\n" + "Any modifications are discarded and the resource is reverted to the original state." @@ -50,7 +49,7 @@ func ManagedByDeclarativeV2(_ context.Context, _ Object, resources []*unstructur lbls = make(map[string]string) } // legacy managed by value - lbls[shared.ManagedBy] = ManagedByLabelValue + lbls[shared.ManagedBy] = shared.KymaLabelValue resource.SetLabels(lbls) } return nil diff --git a/internal/declarative/v2/default_transforms_test.go b/internal/declarative/v2/default_transforms_test.go index 3750ff84eb..8b7a29afae 100644 --- a/internal/declarative/v2/default_transforms_test.go +++ b/internal/declarative/v2/default_transforms_test.go @@ -98,7 +98,7 @@ func Test_defaultTransforms(t *testing.T) { assert.NotEmpty(testingT, unstruct) assert.NotNil(testingT, unstruct.GetLabels()) assert.Contains(testingT, unstruct.GetLabels(), shared.ManagedBy) - assert.Equal(testingT, declarativev2.ManagedByLabelValue, + assert.Equal(testingT, shared.KymaLabelValue, unstruct.GetLabels()[shared.ManagedBy]) return true }, diff --git a/internal/remote/skr_context.go b/internal/remote/skr_context.go index 7c7fd1a060..157507e4ba 100644 --- a/internal/remote/skr_context.go +++ b/internal/remote/skr_context.go @@ -76,7 +76,7 @@ func (s *SkrContext) CreateKymaNamespace(ctx context.Context) error { ObjectMeta: apimetav1.ObjectMeta{ Name: shared.DefaultRemoteNamespace, Labels: map[string]string{ - shared.ManagedBy: shared.OperatorName, + shared.ManagedBy: shared.KymaLabelValue, shared.IstioInjectionLabel: shared.EnabledValue, shared.WardenLabel: shared.EnabledValue, }, diff --git a/pkg/module/common/module.go b/pkg/module/common/module.go index f7c0baa137..39e63f3b22 100644 --- a/pkg/module/common/module.go +++ b/pkg/module/common/module.go @@ -46,7 +46,7 @@ func (m *Module) ApplyDefaultMetaToManifest(kyma *v1beta2.Kyma) { } lbls[shared.ModuleName] = m.ModuleName lbls[shared.ChannelLabel] = m.Template.Spec.Channel - lbls[shared.ManagedBy] = shared.OperatorName + lbls[shared.ManagedBy] = shared.KymaLabelValue if m.Template.Spec.Mandatory { lbls[shared.IsMandatoryModule] = shared.EnableLabelValue } diff --git a/pkg/module/common/module_test.go b/pkg/module/common/module_test.go index d9c91d97a8..e7c6b767ca 100644 --- a/pkg/module/common/module_test.go +++ b/pkg/module/common/module_test.go @@ -72,7 +72,7 @@ func TestApplyDefaultMetaToManifest_WhenCalled_SetsManagedByLabel(t *testing.T) module.ApplyDefaultMetaToManifest(kyma) resultLabels := module.GetLabels() - assert.Equal(t, "lifecycle-manager", resultLabels["operator.kyma-project.io/managed-by"]) + assert.Equal(t, "kyma", resultLabels["operator.kyma-project.io/managed-by"]) } func TestApplyDefaultMetaToManifest_WhenCalled_SetsFQDNAnnotation(t *testing.T) { diff --git a/pkg/testutils/builder/watcher.go b/pkg/testutils/builder/watcher.go index 20c4cb696b..6f3167c922 100644 --- a/pkg/testutils/builder/watcher.go +++ b/pkg/testutils/builder/watcher.go @@ -17,13 +17,16 @@ func NewWatcherBuilder() *WatcherBuilder { builder := &WatcherBuilder{ watcher: &v1beta2.Watcher{}, } + builder.watcher.SetLabels( + map[string]string{ + shared.ManagedBy: shared.KymaLabelValue, + }) namespace := random.Name() return builder. WithName(random.Name()). WithNamespace(namespace). - WithManager(random.Name()). WithServiceInfoName(random.Name()). WithServiceInfoNamespace(namespace). WithServiceInfoPort(random.Port()). @@ -46,13 +49,6 @@ func (builder *WatcherBuilder) WithNamespace(namespace string) *WatcherBuilder { return builder } -func (builder *WatcherBuilder) WithManager(manager string) *WatcherBuilder { - builder.watcher.Labels = map[string]string{ - shared.ManagedBy: manager, - } - return builder -} - func (builder *WatcherBuilder) WithServiceInfoName(name string) *WatcherBuilder { builder.watcher.Spec.ServiceInfo.Name = name return builder diff --git a/pkg/testutils/utils.go b/pkg/testutils/utils.go index 076a3ea265..fc632f4724 100644 --- a/pkg/testutils/utils.go +++ b/pkg/testutils/utils.go @@ -64,7 +64,7 @@ func NewTestIssuer(namespace string) *certmanagerv1.Issuer { Namespace: namespace, Labels: k8slabels.Set{ shared.PurposeLabel: shared.CertManager, - shared.ManagedBy: shared.OperatorName, + shared.ManagedBy: shared.KymaLabelValue, }, }, Spec: certmanagerv1.IssuerSpec{ diff --git a/pkg/watch/mandatory_template_change.go b/pkg/watch/mandatory_template_change.go index aad0e37806..f264f210f6 100644 --- a/pkg/watch/mandatory_template_change.go +++ b/pkg/watch/mandatory_template_change.go @@ -46,7 +46,7 @@ func (h *MandatoryTemplateChangeHandler) Watch() handler.MapFunc { func getKymaList(ctx context.Context, clnt client.Reader) (*v1beta2.KymaList, error) { kymas := &v1beta2.KymaList{} listOptions := &client.ListOptions{ - LabelSelector: k8slabels.SelectorFromSet(k8slabels.Set{shared.ManagedBy: shared.OperatorName}), + LabelSelector: k8slabels.SelectorFromSet(k8slabels.Set{shared.ManagedBy: shared.KymaLabelValue}), } err := clnt.List(ctx, kymas, listOptions) if err != nil { diff --git a/pkg/watcher/certificate_manager.go b/pkg/watcher/certificate_manager.go index 9982e03fbb..7d9336bb7b 100644 --- a/pkg/watcher/certificate_manager.go +++ b/pkg/watcher/certificate_manager.go @@ -86,7 +86,7 @@ func NewCertificateManager(kcpClient client.Client, kymaName string, caCertCache: caCertCache, labelSet: k8slabels.Set{ shared.PurposeLabel: shared.CertManager, - shared.ManagedBy: shared.OperatorName, + shared.ManagedBy: shared.KymaLabelValue, }, } } diff --git a/tests/integration/controller/mandatorymodule/deletion/controller_test.go b/tests/integration/controller/mandatorymodule/deletion/controller_test.go index 2c97654043..1fec192d9d 100644 --- a/tests/integration/controller/mandatorymodule/deletion/controller_test.go +++ b/tests/integration/controller/mandatorymodule/deletion/controller_test.go @@ -92,7 +92,7 @@ func registerControlPlaneLifecycleForKyma(kyma *v1beta2.Kyma) { WithContext(ctx). WithArguments(kcpClient, template).Should(Succeed()) // Set labels and state manual, since we do not start the Kyma Controller - kyma.Labels[shared.ManagedBy] = shared.OperatorName + kyma.Labels[shared.ManagedBy] = shared.KymaLabelValue Eventually(CreateCR). WithContext(ctx). WithArguments(kcpClient, kyma).Should(Succeed()) @@ -102,7 +102,8 @@ func registerControlPlaneLifecycleForKyma(kyma *v1beta2.Kyma) { installName := filepath.Join("main-dir", "installs") mandatoryManifest.Annotations = map[string]string{shared.FQDN: "kyma-project.io/template-operator"} - validImageSpec, err := CreateOCIImageSpecFromFile(installName, server.Listener.Addr().String(), manifestFilePath, + validImageSpec, err := CreateOCIImageSpecFromFile(installName, server.Listener.Addr().String(), + manifestFilePath, false) Expect(err).NotTo(HaveOccurred()) imageSpecByte, err := json.Marshal(validImageSpec) diff --git a/tests/integration/controller/mandatorymodule/installation/controller_test.go b/tests/integration/controller/mandatorymodule/installation/controller_test.go index 2a8343271c..6c3a9bec25 100644 --- a/tests/integration/controller/mandatorymodule/installation/controller_test.go +++ b/tests/integration/controller/mandatorymodule/installation/controller_test.go @@ -88,7 +88,7 @@ func registerControlPlaneLifecycleForKyma(kyma *v1beta2.Kyma) { WithContext(ctx). WithArguments(kcpClient, template).Should(Succeed()) // Set labels and state manual, since we do not start the Kyma Controller - kyma.Labels[shared.ManagedBy] = shared.OperatorName + kyma.Labels[shared.ManagedBy] = shared.KymaLabelValue Eventually(CreateCR). WithContext(ctx). WithArguments(kcpClient, kyma).Should(Succeed()) diff --git a/tests/integration/controller/withwatcher/controller_test.go b/tests/integration/controller/withwatcher/controller_test.go index 3b6c6b0a04..aae4c79d5b 100644 --- a/tests/integration/controller/withwatcher/controller_test.go +++ b/tests/integration/controller/withwatcher/controller_test.go @@ -207,7 +207,7 @@ func verifyWebhookConfig( Name: watcherName, }) } - expectedModuleName, exists := watcherCR.Labels[shared.ManagedBy] + expectedModuleName, exists := watcherCR.Labels[shared.KymaLabelValue] if !exists { return fmt.Errorf("%w: (labels=%v)", ErrManagedByLabelNotFound, watcherCR.Labels) } diff --git a/tests/integration/controller/withwatcher/watcher_controller_helper_test.go b/tests/integration/controller/withwatcher/watcher_controller_helper_test.go index ed39a9cd75..72448e6732 100644 --- a/tests/integration/controller/withwatcher/watcher_controller_helper_test.go +++ b/tests/integration/controller/withwatcher/watcher_controller_helper_test.go @@ -143,7 +143,7 @@ func createCaCertificate() *certmanagerv1.Certificate { SecretName: "klm-watcher-root-secret", SecretTemplate: &certmanagerv1.CertificateSecretTemplate{ Labels: map[string]string{ - shared.ManagedBy: shared.OperatorName, + shared.ManagedBy: shared.KymaLabelValue, }, }, PrivateKey: &certmanagerv1.CertificatePrivateKey{ @@ -167,7 +167,7 @@ func createWatcherCR(managerInstanceName string, statusOnly bool) *v1beta2.Watch Name: managerInstanceName, Namespace: kcpSystemNs, Labels: map[string]string{ - shared.ManagedBy: managerInstanceName, + shared.ManagedBy: shared.KymaLabelValue, }, }, Spec: v1beta2.WatcherSpec{ @@ -198,7 +198,7 @@ func createTLSSecret(kymaObjKey client.ObjectKey) *apicorev1.Secret { Name: watcher.ResolveTLSCertName(kymaObjKey.Name), Namespace: istioSystemNs, Labels: map[string]string{ - shared.ManagedBy: shared.OperatorName, + shared.ManagedBy: shared.KymaLabelValue, }, }, Data: map[string][]byte{ From 5cbacf89968449a0e46f14e5e14db8a1922b9d9c Mon Sep 17 00:00:00 2001 From: "Badr, Nesma" Date: Thu, 26 Sep 2024 13:38:41 +0200 Subject: [PATCH 2/3] test change --- .../controller/mandatorymodule/deletion/controller_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/controller/mandatorymodule/deletion/controller_test.go b/tests/integration/controller/mandatorymodule/deletion/controller_test.go index 1fec192d9d..1ccd32396c 100644 --- a/tests/integration/controller/mandatorymodule/deletion/controller_test.go +++ b/tests/integration/controller/mandatorymodule/deletion/controller_test.go @@ -21,6 +21,7 @@ import ( . "github.com/onsi/gomega" ) +// TEST var ErrNoMandatoryManifest = errors.New("manifest for mandatory Module not found") const ( From ec2bd89d9d844aff43a09a6e06cf5b368f2da57e Mon Sep 17 00:00:00 2001 From: "Badr, Nesma" Date: Thu, 26 Sep 2024 13:40:47 +0200 Subject: [PATCH 3/3] Revert test change --- api/v1beta2/kyma_types.go | 2 +- pkg/module/common/module.go | 2 +- pkg/module/common/module_test.go | 2 +- pkg/testutils/builder/watcher.go | 12 ++++++++---- pkg/testutils/utils.go | 2 +- pkg/watch/mandatory_template_change.go | 2 +- pkg/watcher/certificate_manager.go | 2 +- .../mandatorymodule/deletion/controller_test.go | 3 +-- .../mandatorymodule/installation/controller_test.go | 2 +- .../withwatcher/watcher_controller_helper_test.go | 6 +++--- 10 files changed, 19 insertions(+), 16 deletions(-) diff --git a/api/v1beta2/kyma_types.go b/api/v1beta2/kyma_types.go index 677b195c23..ef50f8e2f8 100644 --- a/api/v1beta2/kyma_types.go +++ b/api/v1beta2/kyma_types.go @@ -435,7 +435,7 @@ func (kyma *Kyma) EnsureLabelsAndFinalizers() bool { } if _, ok := kyma.ObjectMeta.Labels[shared.ManagedBy]; !ok { - kyma.ObjectMeta.Labels[shared.ManagedBy] = shared.KymaLabelValue + kyma.ObjectMeta.Labels[shared.ManagedBy] = shared.OperatorName updateRequired = true } return updateRequired diff --git a/pkg/module/common/module.go b/pkg/module/common/module.go index 39e63f3b22..f7c0baa137 100644 --- a/pkg/module/common/module.go +++ b/pkg/module/common/module.go @@ -46,7 +46,7 @@ func (m *Module) ApplyDefaultMetaToManifest(kyma *v1beta2.Kyma) { } lbls[shared.ModuleName] = m.ModuleName lbls[shared.ChannelLabel] = m.Template.Spec.Channel - lbls[shared.ManagedBy] = shared.KymaLabelValue + lbls[shared.ManagedBy] = shared.OperatorName if m.Template.Spec.Mandatory { lbls[shared.IsMandatoryModule] = shared.EnableLabelValue } diff --git a/pkg/module/common/module_test.go b/pkg/module/common/module_test.go index e7c6b767ca..d9c91d97a8 100644 --- a/pkg/module/common/module_test.go +++ b/pkg/module/common/module_test.go @@ -72,7 +72,7 @@ func TestApplyDefaultMetaToManifest_WhenCalled_SetsManagedByLabel(t *testing.T) module.ApplyDefaultMetaToManifest(kyma) resultLabels := module.GetLabels() - assert.Equal(t, "kyma", resultLabels["operator.kyma-project.io/managed-by"]) + assert.Equal(t, "lifecycle-manager", resultLabels["operator.kyma-project.io/managed-by"]) } func TestApplyDefaultMetaToManifest_WhenCalled_SetsFQDNAnnotation(t *testing.T) { diff --git a/pkg/testutils/builder/watcher.go b/pkg/testutils/builder/watcher.go index 6f3167c922..20c4cb696b 100644 --- a/pkg/testutils/builder/watcher.go +++ b/pkg/testutils/builder/watcher.go @@ -17,16 +17,13 @@ func NewWatcherBuilder() *WatcherBuilder { builder := &WatcherBuilder{ watcher: &v1beta2.Watcher{}, } - builder.watcher.SetLabels( - map[string]string{ - shared.ManagedBy: shared.KymaLabelValue, - }) namespace := random.Name() return builder. WithName(random.Name()). WithNamespace(namespace). + WithManager(random.Name()). WithServiceInfoName(random.Name()). WithServiceInfoNamespace(namespace). WithServiceInfoPort(random.Port()). @@ -49,6 +46,13 @@ func (builder *WatcherBuilder) WithNamespace(namespace string) *WatcherBuilder { return builder } +func (builder *WatcherBuilder) WithManager(manager string) *WatcherBuilder { + builder.watcher.Labels = map[string]string{ + shared.ManagedBy: manager, + } + return builder +} + func (builder *WatcherBuilder) WithServiceInfoName(name string) *WatcherBuilder { builder.watcher.Spec.ServiceInfo.Name = name return builder diff --git a/pkg/testutils/utils.go b/pkg/testutils/utils.go index fc632f4724..076a3ea265 100644 --- a/pkg/testutils/utils.go +++ b/pkg/testutils/utils.go @@ -64,7 +64,7 @@ func NewTestIssuer(namespace string) *certmanagerv1.Issuer { Namespace: namespace, Labels: k8slabels.Set{ shared.PurposeLabel: shared.CertManager, - shared.ManagedBy: shared.KymaLabelValue, + shared.ManagedBy: shared.OperatorName, }, }, Spec: certmanagerv1.IssuerSpec{ diff --git a/pkg/watch/mandatory_template_change.go b/pkg/watch/mandatory_template_change.go index f264f210f6..aad0e37806 100644 --- a/pkg/watch/mandatory_template_change.go +++ b/pkg/watch/mandatory_template_change.go @@ -46,7 +46,7 @@ func (h *MandatoryTemplateChangeHandler) Watch() handler.MapFunc { func getKymaList(ctx context.Context, clnt client.Reader) (*v1beta2.KymaList, error) { kymas := &v1beta2.KymaList{} listOptions := &client.ListOptions{ - LabelSelector: k8slabels.SelectorFromSet(k8slabels.Set{shared.ManagedBy: shared.KymaLabelValue}), + LabelSelector: k8slabels.SelectorFromSet(k8slabels.Set{shared.ManagedBy: shared.OperatorName}), } err := clnt.List(ctx, kymas, listOptions) if err != nil { diff --git a/pkg/watcher/certificate_manager.go b/pkg/watcher/certificate_manager.go index 7d9336bb7b..9982e03fbb 100644 --- a/pkg/watcher/certificate_manager.go +++ b/pkg/watcher/certificate_manager.go @@ -86,7 +86,7 @@ func NewCertificateManager(kcpClient client.Client, kymaName string, caCertCache: caCertCache, labelSet: k8slabels.Set{ shared.PurposeLabel: shared.CertManager, - shared.ManagedBy: shared.KymaLabelValue, + shared.ManagedBy: shared.OperatorName, }, } } diff --git a/tests/integration/controller/mandatorymodule/deletion/controller_test.go b/tests/integration/controller/mandatorymodule/deletion/controller_test.go index 1ccd32396c..963631482a 100644 --- a/tests/integration/controller/mandatorymodule/deletion/controller_test.go +++ b/tests/integration/controller/mandatorymodule/deletion/controller_test.go @@ -21,7 +21,6 @@ import ( . "github.com/onsi/gomega" ) -// TEST var ErrNoMandatoryManifest = errors.New("manifest for mandatory Module not found") const ( @@ -93,7 +92,7 @@ func registerControlPlaneLifecycleForKyma(kyma *v1beta2.Kyma) { WithContext(ctx). WithArguments(kcpClient, template).Should(Succeed()) // Set labels and state manual, since we do not start the Kyma Controller - kyma.Labels[shared.ManagedBy] = shared.KymaLabelValue + kyma.Labels[shared.ManagedBy] = shared.OperatorName Eventually(CreateCR). WithContext(ctx). WithArguments(kcpClient, kyma).Should(Succeed()) diff --git a/tests/integration/controller/mandatorymodule/installation/controller_test.go b/tests/integration/controller/mandatorymodule/installation/controller_test.go index 6c3a9bec25..2a8343271c 100644 --- a/tests/integration/controller/mandatorymodule/installation/controller_test.go +++ b/tests/integration/controller/mandatorymodule/installation/controller_test.go @@ -88,7 +88,7 @@ func registerControlPlaneLifecycleForKyma(kyma *v1beta2.Kyma) { WithContext(ctx). WithArguments(kcpClient, template).Should(Succeed()) // Set labels and state manual, since we do not start the Kyma Controller - kyma.Labels[shared.ManagedBy] = shared.KymaLabelValue + kyma.Labels[shared.ManagedBy] = shared.OperatorName Eventually(CreateCR). WithContext(ctx). WithArguments(kcpClient, kyma).Should(Succeed()) diff --git a/tests/integration/controller/withwatcher/watcher_controller_helper_test.go b/tests/integration/controller/withwatcher/watcher_controller_helper_test.go index 72448e6732..ed39a9cd75 100644 --- a/tests/integration/controller/withwatcher/watcher_controller_helper_test.go +++ b/tests/integration/controller/withwatcher/watcher_controller_helper_test.go @@ -143,7 +143,7 @@ func createCaCertificate() *certmanagerv1.Certificate { SecretName: "klm-watcher-root-secret", SecretTemplate: &certmanagerv1.CertificateSecretTemplate{ Labels: map[string]string{ - shared.ManagedBy: shared.KymaLabelValue, + shared.ManagedBy: shared.OperatorName, }, }, PrivateKey: &certmanagerv1.CertificatePrivateKey{ @@ -167,7 +167,7 @@ func createWatcherCR(managerInstanceName string, statusOnly bool) *v1beta2.Watch Name: managerInstanceName, Namespace: kcpSystemNs, Labels: map[string]string{ - shared.ManagedBy: shared.KymaLabelValue, + shared.ManagedBy: managerInstanceName, }, }, Spec: v1beta2.WatcherSpec{ @@ -198,7 +198,7 @@ func createTLSSecret(kymaObjKey client.ObjectKey) *apicorev1.Secret { Name: watcher.ResolveTLSCertName(kymaObjKey.Name), Namespace: istioSystemNs, Labels: map[string]string{ - shared.ManagedBy: shared.KymaLabelValue, + shared.ManagedBy: shared.OperatorName, }, }, Data: map[string][]byte{