From db4e4bb31f9a3fac50fc74c62efef467e037bfc4 Mon Sep 17 00:00:00 2001 From: Nestorm <859005086@qq.com> Date: Fri, 26 Jul 2024 10:59:09 +0800 Subject: [PATCH] Reduce code and fix webhook cache latency (#361) * add * add * add * fix test * fix comments * fix test * fix helm template * fix helm template generate --------- Co-authored-by: Jiawei Du <59427055+msftcoderdjw@users.noreply.github.com> --- .../containers/v1/generic_container_types.go | 10 ++ k8s/apis/containers/v1/generic_webhook.go | 97 ++++++++++++++ .../containers/v1/zz_generated.deepcopy.go | 50 +++++++ k8s/apis/federation/v1/catalog_types.go | 34 +++++ k8s/apis/federation/v1/catalog_webhook.go | 49 +++++-- .../federation/v1/catalogcontainer_types.go | 39 ------ .../federation/v1/catalogcontainer_webhook.go | 122 ------------------ .../federation/v1/zz_generated.deepcopy.go | 22 ---- k8s/apis/metrics/v1/metrics.go | 13 +- k8s/apis/solution/v1/solution_types.go | 34 +++++ k8s/apis/solution/v1/solution_webhook.go | 46 +++++-- .../solution/v1/solutioncontainer_types.go | 39 ------ .../solution/v1/solutioncontainer_webhook.go | 122 ------------------ k8s/apis/solution/v1/zz_generated.deepcopy.go | 22 ---- k8s/apis/workflow/v1/campaign_types.go | 35 +++++ k8s/apis/workflow/v1/campaign_webhook.go | 47 +++++-- .../workflow/v1/campaigncontainer_types.go | 39 ------ .../workflow/v1/campaigncontainer_webhook.go | 122 ------------------ k8s/apis/workflow/v1/zz_generated.deepcopy.go | 22 ---- .../solution.symphony_solutioncontainers.yaml | 2 +- k8s/main.go | 11 +- .../templates/symphony-core/symphonyk8s.yaml | 2 +- .../04.workflow/verify/manifest_test.go | 2 +- 23 files changed, 390 insertions(+), 591 deletions(-) create mode 100644 k8s/apis/containers/v1/generic_container_types.go create mode 100644 k8s/apis/containers/v1/generic_webhook.go create mode 100644 k8s/apis/containers/v1/zz_generated.deepcopy.go delete mode 100644 k8s/apis/federation/v1/catalogcontainer_types.go delete mode 100644 k8s/apis/federation/v1/catalogcontainer_webhook.go delete mode 100644 k8s/apis/solution/v1/solutioncontainer_types.go delete mode 100644 k8s/apis/solution/v1/solutioncontainer_webhook.go delete mode 100644 k8s/apis/workflow/v1/campaigncontainer_types.go delete mode 100644 k8s/apis/workflow/v1/campaigncontainer_webhook.go diff --git a/k8s/apis/containers/v1/generic_container_types.go b/k8s/apis/containers/v1/generic_container_types.go new file mode 100644 index 000000000..80b785679 --- /dev/null +++ b/k8s/apis/containers/v1/generic_container_types.go @@ -0,0 +1,10 @@ +package v1 + +// +kubebuilder:object:generate=true +type ContainerStatus struct { + Properties map[string]string `json:"properties"` +} + +// +kubebuilder:object:generate=true +type ContainerSpec struct { +} diff --git a/k8s/apis/containers/v1/generic_webhook.go b/k8s/apis/containers/v1/generic_webhook.go new file mode 100644 index 000000000..45ade8992 --- /dev/null +++ b/k8s/apis/containers/v1/generic_webhook.go @@ -0,0 +1,97 @@ +package v1 + +import ( + "context" + "fmt" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + + "github.com/eclipse-symphony/symphony/k8s/apis/metrics/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +type GetSubResourceNums func() (n int, err error) + +var commoncontainerlog = logf.Log.WithName("commoncontainer-resource") +var cacheClient client.Client +var readerClient client.Reader +var commoncontainermetrics *metrics.Metrics + +func InitCommonContainerWebHook(mgr ctrl.Manager) error { + if commoncontainermetrics == nil { + initmetrics, err := metrics.New() + if err != nil { + return err + } + commoncontainermetrics = initmetrics + } + cacheClient = mgr.GetClient() + readerClient = mgr.GetAPIReader() + return nil +} + +func SetupWebhookWithManager(mgr ctrl.Manager, resource client.Object) error { + mgr.GetFieldIndexer().IndexField(context.Background(), resource, ".metadata.name", func(rawObj client.Object) []string { + return []string{rawObj.GetName()} + }) + + return ctrl.NewWebhookManagedBy(mgr). + For(resource). + Complete() +} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func DefaultImpl(r client.Object) { + commoncontainerlog.Info("default", "name", r.GetName(), "kind", r.GetObjectKind()) +} + +func ValidateCreateImpl(r client.Object) (admission.Warnings, error) { + commoncontainerlog.Info("validate create", "name", r.GetName(), "kind", r.GetObjectKind()) + return nil, nil +} +func ValidateUpdateImpl(r client.Object, old runtime.Object) (admission.Warnings, error) { + commoncontainerlog.Info("validate update", "name", r.GetName(), "kind", r.GetObjectKind()) + return nil, nil +} + +func ValidateDeleteImpl(r client.Object, getSubResourceNums GetSubResourceNums) (admission.Warnings, error) { + + commoncontainerlog.Info("validate delete", "name", r.GetName(), "kind", r.GetObjectKind()) + + validateDeleteTime := time.Now() + validationError := validateDeleteContainerImpl(r, getSubResourceNums) + if validationError != nil { + commoncontainermetrics.ControllerValidationLatency( + validateDeleteTime, + metrics.CreateOperationType, + metrics.InvalidResource, + metrics.ContainerResourceType) + } else { + commoncontainermetrics.ControllerValidationLatency( + validateDeleteTime, + metrics.CreateOperationType, + metrics.ValidResource, + metrics.ContainerResourceType) + } + + return nil, validationError +} + +func validateDeleteContainerImpl(r client.Object, getSubResourceNums GetSubResourceNums) error { + itemsNum, err := getSubResourceNums() + if err != nil { + commoncontainerlog.Error(err, "could not list nested resources ", "name", r.GetName(), "kind", r.GetObjectKind()) + return apierrors.NewBadRequest(fmt.Sprintf("%s could not list nested resources for %s.", r.GetObjectKind(), r.GetName())) + } + if itemsNum > 0 { + commoncontainerlog.Error(err, "nested resources are not empty", "name", r.GetName(), "kind", r.GetObjectKind()) + return apierrors.NewBadRequest(fmt.Sprintf("%s nested resources with root resource '%s' are not empty", r.GetObjectKind(), r.GetName())) + } + + return nil +} diff --git a/k8s/apis/containers/v1/zz_generated.deepcopy.go b/k8s/apis/containers/v1/zz_generated.deepcopy.go new file mode 100644 index 000000000..c07054c5c --- /dev/null +++ b/k8s/apis/containers/v1/zz_generated.deepcopy.go @@ -0,0 +1,50 @@ +//go:build !ignore_autogenerated + +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import () + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerSpec) DeepCopyInto(out *ContainerSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSpec. +func (in *ContainerSpec) DeepCopy() *ContainerSpec { + if in == nil { + return nil + } + out := new(ContainerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerStatus) DeepCopyInto(out *ContainerStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerStatus. +func (in *ContainerStatus) DeepCopy() *ContainerStatus { + if in == nil { + return nil + } + out := new(ContainerStatus) + in.DeepCopyInto(out) + return out +} diff --git a/k8s/apis/federation/v1/catalog_types.go b/k8s/apis/federation/v1/catalog_types.go index 4bd445713..e9bd3757e 100644 --- a/k8s/apis/federation/v1/catalog_types.go +++ b/k8s/apis/federation/v1/catalog_types.go @@ -7,9 +7,11 @@ package v1 import ( + commoncontainers "gopls-workspace/apis/containers/v1" k8smodel "gopls-workspace/apis/model/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/webhook" ) type CatalogStatus struct { @@ -35,6 +37,38 @@ type CatalogList struct { Items []Catalog `json:"items"` } +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// CatalogContainer is the Schema for the CatalogContainers API +type CatalogContainer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec commoncontainers.ContainerSpec `json:"spec,omitempty"` + Status commoncontainers.ContainerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// CatalogContainerList contains a list of CatalogContainer +type CatalogContainerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CatalogContainer `json:"items"` +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. + +//+kubebuilder:webhook:path=/validate-federation-symphony-v1-catalogcontainer,mutating=false,failurePolicy=fail,sideEffects=None,groups=federation.symphony,resources=catalogcontainers,verbs=create;update;delete,versions=v1,name=vcatalogcontainer.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &CatalogContainer{} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +//+kubebuilder:webhook:path=/mutate-federation-symphony-v1-catalogcontainer,mutating=true,failurePolicy=fail,sideEffects=None,groups=federation.symphony,resources=catalogcontainers,verbs=create;update,versions=v1,name=mcatalogcontainer.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &CatalogContainer{} + func init() { SchemeBuilder.Register(&Catalog{}, &CatalogList{}) + SchemeBuilder.Register(&CatalogContainer{}, &CatalogContainerList{}) } diff --git a/k8s/apis/federation/v1/catalog_webhook.go b/k8s/apis/federation/v1/catalog_webhook.go index 5c59391c7..f389ae2f5 100644 --- a/k8s/apis/federation/v1/catalog_webhook.go +++ b/k8s/apis/federation/v1/catalog_webhook.go @@ -9,6 +9,7 @@ package v1 import ( "context" "encoding/json" + commoncontainer "gopls-workspace/apis/containers/v1" "gopls-workspace/apis/metrics/v1" "gopls-workspace/configutils" "time" @@ -28,19 +29,15 @@ import ( // log is for logging in this package. var cataloglog = logf.Log.WithName("catalog-resource") -var myCatalogClient client.Client +var myCatalogReaderClient client.Reader var catalogWebhookValidationMetrics *metrics.Metrics func (r *Catalog) SetupWebhookWithManager(mgr ctrl.Manager) error { - myCatalogClient = mgr.GetClient() + myCatalogReaderClient = mgr.GetAPIReader() mgr.GetFieldIndexer().IndexField(context.Background(), &Catalog{}, ".metadata.name", func(rawObj client.Object) []string { catalog := rawObj.(*Catalog) return []string{catalog.Name} }) - mgr.GetFieldIndexer().IndexField(context.Background(), &Catalog{}, ".spec.rootResource", func(rawObj client.Object) []string { - catalog := rawObj.(*Catalog) - return []string{catalog.Spec.RootResource} - }) // initialize the controller operation metrics if catalogWebhookValidationMetrics == nil { @@ -68,13 +65,13 @@ func (r *Catalog) Default() { if r.Spec.RootResource != "" { var catalogContainer CatalogContainer - err := myCatalogClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &catalogContainer) + err := myCatalogReaderClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &catalogContainer) if err != nil { cataloglog.Error(err, "failed to get catalog container", "name", r.Spec.RootResource) } else { ownerReference := metav1.OwnerReference{ - APIVersion: catalogContainer.APIVersion, - Kind: catalogContainer.Kind, + APIVersion: GroupVersion.String(), //catalogContainer.APIVersion + Kind: "CatalogContainer", //catalogContainer.Kind Name: catalogContainer.Name, UID: catalogContainer.UID, } @@ -82,6 +79,11 @@ func (r *Catalog) Default() { if !configutils.CheckOwnerReferenceAlreadySet(r.OwnerReferences, ownerReference) { r.OwnerReferences = append(r.OwnerReferences, ownerReference) } + + if r.Labels == nil { + r.Labels = make(map[string]string) + } + r.Labels["rootResource"] = r.Spec.RootResource } } } @@ -170,7 +172,7 @@ func (r *Catalog) checkSchema() *field.Error { if schemaName, ok := r.Spec.Metadata["schema"]; ok { cataloglog.Info("Find schema name", "name", schemaName) var catalogs CatalogList - err := myCatalogClient.List(context.Background(), &catalogs, client.InNamespace(r.ObjectMeta.Namespace), client.MatchingFields{".metadata.name": schemaName}) + err := myCatalogReaderClient.List(context.Background(), &catalogs, client.InNamespace(r.ObjectMeta.Namespace), client.MatchingFields{"metadata.name": schemaName}, client.Limit(1)) if err != nil || len(catalogs.Items) == 0 { cataloglog.Error(err, "Could not find the required schema.", "name", schemaName) return field.Invalid(field.NewPath("spec").Child("Metadata"), schemaName, "could not find the required schema") @@ -236,7 +238,7 @@ func (r *Catalog) validateNameOnCreate() *field.Error { func (r *Catalog) validateRootResource() *field.Error { var catalogContainer CatalogContainer - err := myCatalogClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &catalogContainer) + err := myCatalogReaderClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &catalogContainer) if err != nil { return field.Invalid(field.NewPath("spec").Child("rootResource"), r.Spec.RootResource, "rootResource must be a valid catalog container") } @@ -247,3 +249,28 @@ func (r *Catalog) validateRootResource() *field.Error { return nil } + +func (r *CatalogContainer) Default() { + commoncontainer.DefaultImpl(r) +} + +func (r *CatalogContainer) ValidateCreate() (admission.Warnings, error) { + return commoncontainer.ValidateCreateImpl(r) +} +func (r *CatalogContainer) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + return commoncontainer.ValidateUpdateImpl(r, old) +} + +func (r *CatalogContainer) ValidateDelete() (admission.Warnings, error) { + cataloglog.Info("validate delete catalog container", "name", r.Name) + getSubResourceNums := func() (int, error) { + var catalogList CatalogList + err := myCatalogReaderClient.List(context.Background(), &catalogList, client.InNamespace(r.Namespace), client.MatchingLabels{"rootResource": r.Name}, client.Limit(1)) + if err != nil { + return 0, err + } else { + return len(catalogList.Items), nil + } + } + return commoncontainer.ValidateDeleteImpl(r, getSubResourceNums) +} diff --git a/k8s/apis/federation/v1/catalogcontainer_types.go b/k8s/apis/federation/v1/catalogcontainer_types.go deleted file mode 100644 index 11bfdf8e4..000000000 --- a/k8s/apis/federation/v1/catalogcontainer_types.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * SPDX-License-Identifier: MIT - */ - -package v1 - -import ( - k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type CatalogContainerStatus struct { - Properties map[string]string `json:"properties"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// CatalogContainer is the Schema for the CatalogContainers API -type CatalogContainer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec k8smodel.CatalogContainerSpec `json:"spec,omitempty"` - Status CatalogContainerStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true -// CatalogContainerList contains a list of CatalogContainer -type CatalogContainerList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []CatalogContainer `json:"items"` -} - -func init() { - SchemeBuilder.Register(&CatalogContainer{}, &CatalogContainerList{}) -} diff --git a/k8s/apis/federation/v1/catalogcontainer_webhook.go b/k8s/apis/federation/v1/catalogcontainer_webhook.go deleted file mode 100644 index a2a05f88a..000000000 --- a/k8s/apis/federation/v1/catalogcontainer_webhook.go +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * SPDX-License-Identifier: MIT - */ - -package v1 - -import ( - "context" - "fmt" - "gopls-workspace/apis/metrics/v1" - "time" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var catalogcontainerlog = logf.Log.WithName("catalogcontainer-resource") -var myCatalogContainerClient client.Client -var catalogContainerWebhookValidationMetrics *metrics.Metrics - -func (r *CatalogContainer) SetupWebhookWithManager(mgr ctrl.Manager) error { - myCatalogContainerClient = mgr.GetClient() - mgr.GetFieldIndexer().IndexField(context.Background(), &CatalogContainer{}, ".metadata.name", func(rawObj client.Object) []string { - catalog := rawObj.(*CatalogContainer) - return []string{catalog.Name} - }) - - // initialize the controller operation metrics - if catalogContainerWebhookValidationMetrics == nil { - metrics, err := metrics.New() - if err != nil { - return err - } - catalogContainerWebhookValidationMetrics = metrics - } - - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! - -//+kubebuilder:webhook:path=/mutate-federation-symphony-v1-catalogcontainer,mutating=true,failurePolicy=fail,sideEffects=None,groups=federation.symphony,resources=catalogcontainers,verbs=create;update,versions=v1,name=mcatalogcontainer.kb.io,admissionReviewVersions=v1 - -var _ webhook.Defaulter = &CatalogContainer{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *CatalogContainer) Default() { - catalogcontainerlog.Info("default", "name", r.Name) -} - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. - -//+kubebuilder:webhook:path=/validate-federation-symphony-v1-catalogcontainer,mutating=false,failurePolicy=fail,sideEffects=None,groups=federation.symphony,resources=catalogcontainers,verbs=create;update;delete,versions=v1,name=vcatalogcontainer.kb.io,admissionReviewVersions=v1 - -var _ webhook.Validator = &CatalogContainer{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *CatalogContainer) ValidateCreate() (admission.Warnings, error) { - catalogcontainerlog.Info("validate create", "name", r.Name) - - return nil, nil -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *CatalogContainer) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - catalogcontainerlog.Info("validate update", "name", r.Name) - - return nil, nil -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *CatalogContainer) ValidateDelete() (admission.Warnings, error) { - catalogcontainerlog.Info("validate delete", "name", r.Name) - - validateDeleteTime := time.Now() - validationError := r.validateDeleteCatalogContainer() - if validationError != nil { - catalogContainerWebhookValidationMetrics.ControllerValidationLatency( - validateDeleteTime, - metrics.CreateOperationType, - metrics.InvalidResource, - metrics.CatalogResourceType) - } else { - catalogContainerWebhookValidationMetrics.ControllerValidationLatency( - validateDeleteTime, - metrics.CreateOperationType, - metrics.ValidResource, - metrics.CatalogResourceType) - } - - return nil, validationError -} - -func (r *CatalogContainer) validateDeleteCatalogContainer() error { - return r.validateCatalogs() -} - -func (r *CatalogContainer) validateCatalogs() error { - var catalog CatalogList - err := myCatalogContainerClient.List(context.Background(), &catalog, client.InNamespace(r.Namespace), client.MatchingFields{".spec.rootResource": r.Name}) - if err != nil { - catalogcontainerlog.Error(err, "could not list catalogs", "name", r.Name) - return apierrors.NewBadRequest(fmt.Sprintf("could not list catalogs for catalog container %s.", r.Name)) - } - - if len(catalog.Items) != 0 { - catalogcontainerlog.Error(err, "catalogs are not empty", "name", r.Name) - return apierrors.NewBadRequest(fmt.Sprintf("catalogs with root resource '%s' are not empty", r.Name)) - } - - return nil -} diff --git a/k8s/apis/federation/v1/zz_generated.deepcopy.go b/k8s/apis/federation/v1/zz_generated.deepcopy.go index a1940aad2..aeeb5dd5f 100644 --- a/k8s/apis/federation/v1/zz_generated.deepcopy.go +++ b/k8s/apis/federation/v1/zz_generated.deepcopy.go @@ -100,28 +100,6 @@ func (in *CatalogContainerList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CatalogContainerStatus) DeepCopyInto(out *CatalogContainerStatus) { - *out = *in - if in.Properties != nil { - in, out := &in.Properties, &out.Properties - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogContainerStatus. -func (in *CatalogContainerStatus) DeepCopy() *CatalogContainerStatus { - if in == nil { - return nil - } - out := new(CatalogContainerStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CatalogList) DeepCopyInto(out *CatalogList) { *out = *in diff --git a/k8s/apis/metrics/v1/metrics.go b/k8s/apis/metrics/v1/metrics.go index 6a87a7984..2806e1cf2 100644 --- a/k8s/apis/metrics/v1/metrics.go +++ b/k8s/apis/metrics/v1/metrics.go @@ -21,12 +21,13 @@ const ( ValidResource string = "Valid" InvalidResource string = "Invalid" //resource type - TargetResourceType string = "Target" - InstanceResourceType string = "Instance" - CatalogResourceType string = "Catalog" - ModelResourceType string = "Model" - SkillResourceType string = "Skill" - DeviceResourceType string = "Device" + TargetResourceType string = "Target" + InstanceResourceType string = "Instance" + CatalogResourceType string = "Catalog" + ContainerResourceType string = "Container" + ModelResourceType string = "Model" + SkillResourceType string = "Skill" + DeviceResourceType string = "Device" ) // Metrics is a metrics tracker for a controller operation. diff --git a/k8s/apis/solution/v1/solution_types.go b/k8s/apis/solution/v1/solution_types.go index 385d45e0f..46ad9f237 100644 --- a/k8s/apis/solution/v1/solution_types.go +++ b/k8s/apis/solution/v1/solution_types.go @@ -7,9 +7,11 @@ package v1 import ( + cc "gopls-workspace/apis/containers/v1" k8smodel "gopls-workspace/apis/model/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/webhook" ) // SolutionStatus defines the observed state of Solution @@ -37,6 +39,38 @@ type SolutionList struct { Items []Solution `json:"items"` } +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// SolutionContainer is the Schema for the SolutionContainers API +type SolutionContainer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec cc.ContainerSpec `json:"spec,omitempty"` + Status cc.ContainerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// SolutionContainerList contains a list of SolutionContainer +type SolutionContainerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SolutionContainer `json:"items"` +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. + +//+kubebuilder:webhook:path=/validate-solution-symphony-v1-solutioncontainer,mutating=false,failurePolicy=fail,sideEffects=None,groups=solution.symphony,resources=solutioncontainers,verbs=create;update;delete,versions=v1,name=vsolutioncontainer.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &SolutionContainer{} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +//+kubebuilder:webhook:path=/mutate-solution-symphony-v1-solutioncontainer,mutating=true,failurePolicy=fail,sideEffects=None,groups=solution.symphony,resources=solutioncontainers,verbs=create;update,versions=v1,name=msolutioncontainer.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &SolutionContainer{} + func init() { SchemeBuilder.Register(&Solution{}, &SolutionList{}) + SchemeBuilder.Register(&SolutionContainer{}, &SolutionContainerList{}) } diff --git a/k8s/apis/solution/v1/solution_webhook.go b/k8s/apis/solution/v1/solution_webhook.go index 20f617690..1a255dd53 100644 --- a/k8s/apis/solution/v1/solution_webhook.go +++ b/k8s/apis/solution/v1/solution_webhook.go @@ -9,6 +9,7 @@ package v1 import ( "context" "fmt" + commoncontainer "gopls-workspace/apis/containers/v1" "gopls-workspace/configutils" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -26,18 +27,15 @@ import ( // log is for logging in this package. var solutionlog = logf.Log.WithName("solution-resource") var mySolutionClient client.Client +var mySolutionReaderClient client.Reader func (r *Solution) SetupWebhookWithManager(mgr ctrl.Manager) error { mySolutionClient = mgr.GetClient() + mySolutionReaderClient = mgr.GetAPIReader() mgr.GetFieldIndexer().IndexField(context.Background(), &Solution{}, ".spec.displayName", func(rawObj client.Object) []string { solution := rawObj.(*Solution) return []string{solution.Spec.DisplayName} }) - mgr.GetFieldIndexer().IndexField(context.Background(), &Solution{}, ".spec.rootResource", func(rawObj client.Object) []string { - solution := rawObj.(*Solution) - return []string{solution.Spec.RootResource} - }) - return ctrl.NewWebhookManagedBy(mgr). For(r). Complete() @@ -64,8 +62,8 @@ func (r *Solution) Default() { solutionlog.Error(err, "failed to get solution container", "name", r.Spec.RootResource) } else { ownerReference := metav1.OwnerReference{ - APIVersion: solutionContainer.APIVersion, - Kind: solutionContainer.Kind, + APIVersion: GroupVersion.String(), + Kind: "SolutionContainer", Name: solutionContainer.Name, UID: solutionContainer.UID, } @@ -73,6 +71,11 @@ func (r *Solution) Default() { if !configutils.CheckOwnerReferenceAlreadySet(r.OwnerReferences, ownerReference) { r.OwnerReferences = append(r.OwnerReferences, ownerReference) } + + if r.Labels == nil { + r.Labels = make(map[string]string) + } + r.Labels["rootResource"] = r.Spec.RootResource } } } @@ -126,7 +129,7 @@ func (r *Solution) validateCreateSolution() error { func (r *Solution) validateUpdateSolution() error { var solutions SolutionList - err := mySolutionClient.List(context.Background(), &solutions, client.InNamespace(r.Namespace), client.MatchingFields{".spec.displayName": r.Spec.DisplayName}) + err := mySolutionClient.List(context.Background(), &solutions, client.InNamespace(r.Namespace), client.MatchingFields{".spec.displayName": r.Spec.DisplayName}, client.Limit(2)) if err != nil { return apierrors.NewInternalError(err) } @@ -138,7 +141,7 @@ func (r *Solution) validateUpdateSolution() error { func (r *Solution) validateUniqueNameOnCreate() *field.Error { var solutions SolutionList - err := mySolutionClient.List(context.Background(), &solutions, client.InNamespace(r.Namespace), client.MatchingFields{".spec.displayName": r.Spec.DisplayName}) + err := mySolutionClient.List(context.Background(), &solutions, client.InNamespace(r.Namespace), client.MatchingFields{".spec.displayName": r.Spec.DisplayName}, client.Limit(1)) if err != nil { return field.InternalError(&field.Path{}, err) } @@ -167,3 +170,28 @@ func (r *Solution) validateRootResource() *field.Error { return nil } + +func (r *SolutionContainer) Default() { + commoncontainer.DefaultImpl(r) +} + +func (r *SolutionContainer) ValidateCreate() (admission.Warnings, error) { + return commoncontainer.ValidateCreateImpl(r) +} +func (r *SolutionContainer) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + return commoncontainer.ValidateUpdateImpl(r, old) +} + +func (r *SolutionContainer) ValidateDelete() (admission.Warnings, error) { + solutionlog.Info("validate delete solution container", "name", r.Name) + getSubResourceNums := func() (int, error) { + var solutionList SolutionList + err := mySolutionReaderClient.List(context.Background(), &solutionList, client.InNamespace(r.Namespace), client.MatchingLabels{"rootResource": r.Name}, client.Limit(1)) + if err != nil { + return 0, err + } else { + return len(solutionList.Items), nil + } + } + return commoncontainer.ValidateDeleteImpl(r, getSubResourceNums) +} diff --git a/k8s/apis/solution/v1/solutioncontainer_types.go b/k8s/apis/solution/v1/solutioncontainer_types.go deleted file mode 100644 index 1c2a45907..000000000 --- a/k8s/apis/solution/v1/solutioncontainer_types.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * SPDX-License-Identifier: MIT - */ - -package v1 - -import ( - k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type SolutionContainerStatus struct { - Properties map[string]string `json:"properties"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// SolutionContainer is the Schema for the SolutionContainer API -type SolutionContainer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec k8smodel.SolutionContainerSpec `json:"spec,omitempty"` - Status SolutionContainerStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true -// SolutionContainerList contains a list of SolutionContainer -type SolutionContainerList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []SolutionContainer `json:"items"` -} - -func init() { - SchemeBuilder.Register(&SolutionContainer{}, &SolutionContainerList{}) -} diff --git a/k8s/apis/solution/v1/solutioncontainer_webhook.go b/k8s/apis/solution/v1/solutioncontainer_webhook.go deleted file mode 100644 index 76c5414fb..000000000 --- a/k8s/apis/solution/v1/solutioncontainer_webhook.go +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * SPDX-License-Identifier: MIT - */ - -package v1 - -import ( - "context" - "fmt" - "gopls-workspace/apis/metrics/v1" - "time" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var solutioncontainerlog = logf.Log.WithName("solutioncontainer-resource") -var mySolutionContainerClient client.Client -var solutionContainerWebhookValidationMetrics *metrics.Metrics - -func (r *SolutionContainer) SetupWebhookWithManager(mgr ctrl.Manager) error { - mySolutionContainerClient = mgr.GetClient() - mgr.GetFieldIndexer().IndexField(context.Background(), &SolutionContainer{}, ".metadata.name", func(rawObj client.Object) []string { - solution := rawObj.(*SolutionContainer) - return []string{solution.Name} - }) - - // initialize the controller operation metrics - if solutionContainerWebhookValidationMetrics == nil { - metrics, err := metrics.New() - if err != nil { - return err - } - solutionContainerWebhookValidationMetrics = metrics - } - - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! - -//+kubebuilder:webhook:path=/mutate-solution-symphony-v1-solutioncontainer,mutating=true,failurePolicy=fail,sideEffects=None,groups=solution.symphony,resources=solutioncontainers,verbs=create;update,versions=v1,name=msolutioncontainer.kb.io,admissionReviewVersions=v1 - -var _ webhook.Defaulter = &SolutionContainer{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *SolutionContainer) Default() { - solutioncontainerlog.Info("default", "name", r.Name) -} - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. - -//+kubebuilder:webhook:path=/validate-solution-symphony-v1-solutioncontainer,mutating=false,failurePolicy=fail,sideEffects=None,groups=solution.symphony,resources=solutioncontainers,verbs=create;update;delete,versions=v1,name=vsolutioncontainer.kb.io,admissionReviewVersions=v1 - -var _ webhook.Validator = &SolutionContainer{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *SolutionContainer) ValidateCreate() (admission.Warnings, error) { - solutioncontainerlog.Info("validate create", "name", r.Name) - - return nil, nil -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *SolutionContainer) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - solutioncontainerlog.Info("validate update", "name", r.Name) - - return nil, nil -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *SolutionContainer) ValidateDelete() (admission.Warnings, error) { - solutioncontainerlog.Info("validate delete", "name", r.Name) - - validateDeleteTime := time.Now() - validationError := r.validateDeleteSolutionContainer() - if validationError != nil { - solutionContainerWebhookValidationMetrics.ControllerValidationLatency( - validateDeleteTime, - metrics.CreateOperationType, - metrics.InvalidResource, - metrics.CatalogResourceType) - } else { - solutionContainerWebhookValidationMetrics.ControllerValidationLatency( - validateDeleteTime, - metrics.CreateOperationType, - metrics.ValidResource, - metrics.CatalogResourceType) - } - - return nil, validationError -} - -func (r *SolutionContainer) validateDeleteSolutionContainer() error { - return r.validateSolutions() -} - -func (r *SolutionContainer) validateSolutions() error { - var solution SolutionList - err := mySolutionContainerClient.List(context.Background(), &solution, client.InNamespace(r.Namespace), client.MatchingFields{".spec.rootResource": r.Name}) - if err != nil { - solutioncontainerlog.Error(err, "could not list solutions", "name", r.Name) - return apierrors.NewBadRequest(fmt.Sprintf("could not list solutions for solution container %s.", r.Name)) - } - - if len(solution.Items) != 0 { - solutioncontainerlog.Error(err, "solutions are not empty", "name", r.Name) - return apierrors.NewBadRequest(fmt.Sprintf("solutions with root resource '%s' are not empty", r.Name)) - } - - return nil -} diff --git a/k8s/apis/solution/v1/zz_generated.deepcopy.go b/k8s/apis/solution/v1/zz_generated.deepcopy.go index ac50b38b6..2459220b7 100644 --- a/k8s/apis/solution/v1/zz_generated.deepcopy.go +++ b/k8s/apis/solution/v1/zz_generated.deepcopy.go @@ -159,28 +159,6 @@ func (in *SolutionContainerList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SolutionContainerStatus) DeepCopyInto(out *SolutionContainerStatus) { - *out = *in - if in.Properties != nil { - in, out := &in.Properties, &out.Properties - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SolutionContainerStatus. -func (in *SolutionContainerStatus) DeepCopy() *SolutionContainerStatus { - if in == nil { - return nil - } - out := new(SolutionContainerStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SolutionList) DeepCopyInto(out *SolutionList) { *out = *in diff --git a/k8s/apis/workflow/v1/campaign_types.go b/k8s/apis/workflow/v1/campaign_types.go index 1ace718fc..9a6efec8f 100644 --- a/k8s/apis/workflow/v1/campaign_types.go +++ b/k8s/apis/workflow/v1/campaign_types.go @@ -9,7 +9,10 @@ package v1 import ( k8smodel "gopls-workspace/apis/model/v1" + commoncontainers "gopls-workspace/apis/containers/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/webhook" ) // CampaignStatus defines the observed state of Campaign @@ -37,6 +40,38 @@ type CampaignList struct { Items []Campaign `json:"items"` } +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// CampaignContainer is the Schema for the CampaignContainers API +type CampaignContainer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec commoncontainers.ContainerSpec `json:"spec,omitempty"` + Status commoncontainers.ContainerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// CampaignContainerList contains a list of CampaignContainer +type CampaignContainerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CampaignContainer `json:"items"` +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. + +//+kubebuilder:webhook:path=/validate-workflow-symphony-v1-campaigncontainer,mutating=false,failurePolicy=fail,sideEffects=None,groups=workflow.symphony,resources=campaigncontainers,verbs=create;update;delete,versions=v1,name=vcampaigncontainer.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &CampaignContainer{} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +//+kubebuilder:webhook:path=/mutate-workflow-symphony-v1-campaigncontainer,mutating=true,failurePolicy=fail,sideEffects=None,groups=workflow.symphony,resources=campaigncontainers,verbs=create;update,versions=v1,name=mcampaigncontainer.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &CampaignContainer{} + func init() { SchemeBuilder.Register(&Campaign{}, &CampaignList{}) + SchemeBuilder.Register(&CampaignContainer{}, &CampaignContainerList{}) } diff --git a/k8s/apis/workflow/v1/campaign_webhook.go b/k8s/apis/workflow/v1/campaign_webhook.go index 8eb352f12..ba67b14ce 100644 --- a/k8s/apis/workflow/v1/campaign_webhook.go +++ b/k8s/apis/workflow/v1/campaign_webhook.go @@ -8,6 +8,7 @@ package v1 import ( "context" + commoncontainer "gopls-workspace/apis/containers/v1" "gopls-workspace/apis/metrics/v1" "gopls-workspace/configutils" "time" @@ -26,19 +27,15 @@ import ( // log is for logging in this package. var campaignlog = logf.Log.WithName("campaign-resource") -var myCampaignClient client.Client +var myCampaignReaderClient client.Reader var catalogWebhookValidationMetrics *metrics.Metrics func (r *Campaign) SetupWebhookWithManager(mgr ctrl.Manager) error { - myCampaignClient = mgr.GetClient() + myCampaignReaderClient = mgr.GetAPIReader() mgr.GetFieldIndexer().IndexField(context.Background(), &Campaign{}, ".metadata.name", func(rawObj client.Object) []string { campaign := rawObj.(*Campaign) return []string{campaign.Name} }) - mgr.GetFieldIndexer().IndexField(context.Background(), &Campaign{}, ".spec.rootResource", func(rawObj client.Object) []string { - campaign := rawObj.(*Campaign) - return []string{campaign.Spec.RootResource} - }) // initialize the controller operation metrics if catalogWebhookValidationMetrics == nil { @@ -66,13 +63,13 @@ func (r *Campaign) Default() { if r.Spec.RootResource != "" { var campaignContainer CampaignContainer - err := myCampaignClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &campaignContainer) + err := myCampaignReaderClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &campaignContainer) if err != nil { campaignlog.Error(err, "failed to get campaign container", "name", r.Spec.RootResource) } else { ownerReference := metav1.OwnerReference{ - APIVersion: campaignContainer.APIVersion, - Kind: campaignContainer.Kind, + APIVersion: GroupVersion.String(), //campaignContainer.APIVersion + Kind: "CampaignContainer", //campaignContainer.Kind Name: campaignContainer.Name, UID: campaignContainer.UID, } @@ -80,6 +77,11 @@ func (r *Campaign) Default() { if !configutils.CheckOwnerReferenceAlreadySet(r.OwnerReferences, ownerReference) { r.OwnerReferences = append(r.OwnerReferences, ownerReference) } + + if r.Labels == nil { + r.Labels = make(map[string]string) + } + r.Labels["rootResource"] = r.Spec.RootResource } } } @@ -150,7 +152,7 @@ func (r *Campaign) validateNameOnCreate() *field.Error { func (r *Campaign) validateRootResource() *field.Error { var campaignContainer CampaignContainer - err := myCampaignClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &campaignContainer) + err := myCampaignReaderClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &campaignContainer) if err != nil { return field.Invalid(field.NewPath("spec").Child("rootResource"), r.Spec.RootResource, "rootResource must be a valid campaign container") } @@ -161,3 +163,28 @@ func (r *Campaign) validateRootResource() *field.Error { return nil } + +func (r *CampaignContainer) Default() { + commoncontainer.DefaultImpl(r) +} + +func (r *CampaignContainer) ValidateCreate() (admission.Warnings, error) { + return commoncontainer.ValidateCreateImpl(r) +} +func (r *CampaignContainer) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + return commoncontainer.ValidateUpdateImpl(r, old) +} + +func (r *CampaignContainer) ValidateDelete() (admission.Warnings, error) { + campaignlog.Info("validate delete campaign container", "name", r.Name) + getSubResourceNums := func() (int, error) { + var campaignList CampaignList + err := myCampaignReaderClient.List(context.Background(), &campaignList, client.InNamespace(r.Namespace), client.MatchingLabels{"rootResource": r.Name}, client.Limit(1)) + if err != nil { + return 0, err + } else { + return len(campaignList.Items), nil + } + } + return commoncontainer.ValidateDeleteImpl(r, getSubResourceNums) +} diff --git a/k8s/apis/workflow/v1/campaigncontainer_types.go b/k8s/apis/workflow/v1/campaigncontainer_types.go deleted file mode 100644 index e49540515..000000000 --- a/k8s/apis/workflow/v1/campaigncontainer_types.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * SPDX-License-Identifier: MIT - */ - -package v1 - -import ( - k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type CampaignContainerStatus struct { - Properties map[string]string `json:"properties"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// CampaignContainer is the Schema for the CampaignContainers API -type CampaignContainer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec k8smodel.CampaignContainerSpec `json:"spec,omitempty"` - Status CampaignContainerStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true -// CampaignContainerList contains a list of CampaignContainer -type CampaignContainerList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []CampaignContainer `json:"items"` -} - -func init() { - SchemeBuilder.Register(&CampaignContainer{}, &CampaignContainerList{}) -} diff --git a/k8s/apis/workflow/v1/campaigncontainer_webhook.go b/k8s/apis/workflow/v1/campaigncontainer_webhook.go deleted file mode 100644 index 79a3359bb..000000000 --- a/k8s/apis/workflow/v1/campaigncontainer_webhook.go +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * SPDX-License-Identifier: MIT - */ - -package v1 - -import ( - "context" - "fmt" - "gopls-workspace/apis/metrics/v1" - "time" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var campaigncontainerlog = logf.Log.WithName("campaigncontainer-resource") -var myCampaignContainerClient client.Client -var campaignContainerWebhookValidationMetrics *metrics.Metrics - -func (r *CampaignContainer) SetupWebhookWithManager(mgr ctrl.Manager) error { - myCampaignContainerClient = mgr.GetClient() - mgr.GetFieldIndexer().IndexField(context.Background(), &CampaignContainer{}, ".metadata.name", func(rawObj client.Object) []string { - campaign := rawObj.(*CampaignContainer) - return []string{campaign.Name} - }) - - // initialize the controller operation metrics - if campaignContainerWebhookValidationMetrics == nil { - metrics, err := metrics.New() - if err != nil { - return err - } - campaignContainerWebhookValidationMetrics = metrics - } - - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! - -//+kubebuilder:webhook:path=/mutate-workflow-symphony-v1-campaigncontainer,mutating=true,failurePolicy=fail,sideEffects=None,groups=workflow.symphony,resources=campaigncontainers,verbs=create;update,versions=v1,name=mcampaigncontainer.kb.io,admissionReviewVersions=v1 - -var _ webhook.Defaulter = &CampaignContainer{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *CampaignContainer) Default() { - campaigncontainerlog.Info("default", "name", r.Name) -} - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. - -//+kubebuilder:webhook:path=/validate-workflow-symphony-v1-campaigncontainer,mutating=false,failurePolicy=fail,sideEffects=None,groups=workflow.symphony,resources=campaigncontainers,verbs=create;update;delete,versions=v1,name=vcampaigncontainer.kb.io,admissionReviewVersions=v1 - -var _ webhook.Validator = &CampaignContainer{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *CampaignContainer) ValidateCreate() (admission.Warnings, error) { - campaigncontainerlog.Info("validate create", "name", r.Name) - - return nil, nil -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *CampaignContainer) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - campaigncontainerlog.Info("validate update", "name", r.Name) - - return nil, nil -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *CampaignContainer) ValidateDelete() (admission.Warnings, error) { - campaigncontainerlog.Info("validate delete", "name", r.Name) - - validateDeleteTime := time.Now() - validationError := r.validateDeleteCampaignContainer() - if validationError != nil { - campaignContainerWebhookValidationMetrics.ControllerValidationLatency( - validateDeleteTime, - metrics.CreateOperationType, - metrics.InvalidResource, - metrics.CatalogResourceType) - } else { - campaignContainerWebhookValidationMetrics.ControllerValidationLatency( - validateDeleteTime, - metrics.CreateOperationType, - metrics.ValidResource, - metrics.CatalogResourceType) - } - - return nil, validationError -} - -func (r *CampaignContainer) validateDeleteCampaignContainer() error { - return r.validateCampaigns() -} - -func (r *CampaignContainer) validateCampaigns() error { - var campaign CampaignList - err := myCampaignContainerClient.List(context.Background(), &campaign, client.InNamespace(r.Namespace), client.MatchingFields{".spec.rootResource": r.Name}) - if err != nil { - campaigncontainerlog.Error(err, "could not list campaigns", "name", r.Name) - return apierrors.NewBadRequest(fmt.Sprintf("could not list campaigns for campaign container %s.", r.Name)) - } - - if len(campaign.Items) != 0 { - campaigncontainerlog.Error(err, "campaigns are not empty", "name", r.Name) - return apierrors.NewBadRequest(fmt.Sprintf("campaigns with root resource '%s' are not empty", r.Name)) - } - - return nil -} diff --git a/k8s/apis/workflow/v1/zz_generated.deepcopy.go b/k8s/apis/workflow/v1/zz_generated.deepcopy.go index cd1aacf9c..82f06d171 100644 --- a/k8s/apis/workflow/v1/zz_generated.deepcopy.go +++ b/k8s/apis/workflow/v1/zz_generated.deepcopy.go @@ -175,28 +175,6 @@ func (in *CampaignContainerList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CampaignContainerStatus) DeepCopyInto(out *CampaignContainerStatus) { - *out = *in - if in.Properties != nil { - in, out := &in.Properties, &out.Properties - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CampaignContainerStatus. -func (in *CampaignContainerStatus) DeepCopy() *CampaignContainerStatus { - if in == nil { - return nil - } - out := new(CampaignContainerStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CampaignList) DeepCopyInto(out *CampaignList) { *out = *in diff --git a/k8s/config/oss/crd/bases/solution.symphony_solutioncontainers.yaml b/k8s/config/oss/crd/bases/solution.symphony_solutioncontainers.yaml index 9d97d64cc..e8e4a4008 100644 --- a/k8s/config/oss/crd/bases/solution.symphony_solutioncontainers.yaml +++ b/k8s/config/oss/crd/bases/solution.symphony_solutioncontainers.yaml @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: SolutionContainer is the Schema for the SolutionContainer API + description: SolutionContainer is the Schema for the SolutionContainers API properties: apiVersion: description: |- diff --git a/k8s/main.go b/k8s/main.go index d5c1d0101..4af9b3b4b 100644 --- a/k8s/main.go +++ b/k8s/main.go @@ -31,6 +31,7 @@ import ( aiv1 "gopls-workspace/apis/ai/v1" configv1 "gopls-workspace/apis/config/v1" + commoncontainer "gopls-workspace/apis/containers/v1" fabricv1 "gopls-workspace/apis/fabric/v1" federationv1 "gopls-workspace/apis/federation/v1" solutionv1 "gopls-workspace/apis/solution/v1" @@ -306,15 +307,19 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "Campaign") os.Exit(1) } - if err = (&workflowv1.CampaignContainer{}).SetupWebhookWithManager(mgr); err != nil { + if err = commoncontainer.InitCommonContainerWebHook(mgr); err != nil { + setupLog.Error(err, "unable to Init Common Conainer", "webhook", "Common Conainer") + os.Exit(1) + } + if err = commoncontainer.SetupWebhookWithManager(mgr, &workflowv1.CampaignContainer{}); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "CampaignContainer") os.Exit(1) } - if err = (&federationv1.CatalogContainer{}).SetupWebhookWithManager(mgr); err != nil { + if err = commoncontainer.SetupWebhookWithManager(mgr, &federationv1.CatalogContainer{}); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "CatalogContainer") os.Exit(1) } - if err = (&solutionv1.SolutionContainer{}).SetupWebhookWithManager(mgr); err != nil { + if err = commoncontainer.SetupWebhookWithManager(mgr, &solutionv1.SolutionContainer{}); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "SolutionContainer") os.Exit(1) } diff --git a/packages/helm/symphony/templates/symphony-core/symphonyk8s.yaml b/packages/helm/symphony/templates/symphony-core/symphonyk8s.yaml index 682b7e327..cc061236f 100644 --- a/packages/helm/symphony/templates/symphony-core/symphonyk8s.yaml +++ b/packages/helm/symphony/templates/symphony-core/symphonyk8s.yaml @@ -1237,7 +1237,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: SolutionContainer is the Schema for the SolutionContainer API + description: SolutionContainer is the Schema for the SolutionContainers API properties: apiVersion: description: |- diff --git a/test/integration/scenarios/04.workflow/verify/manifest_test.go b/test/integration/scenarios/04.workflow/verify/manifest_test.go index 185dace34..047e50dd3 100644 --- a/test/integration/scenarios/04.workflow/verify/manifest_test.go +++ b/test/integration/scenarios/04.workflow/verify/manifest_test.go @@ -455,7 +455,7 @@ func getLabels(resource unstructured.Unstructured) string { return "wronglabel" } } else { - return "wronglabel" + return "nolabel" } } else { return "nolabel"