diff --git a/apis/apps/v1alpha1/cluster_types.go b/apis/apps/v1alpha1/cluster_types.go index ed505b5c7e3..20cbe49fed5 100644 --- a/apis/apps/v1alpha1/cluster_types.go +++ b/apis/apps/v1alpha1/cluster_types.go @@ -589,8 +589,9 @@ type ClusterComponentSpec struct { // +optional ComponentDefRef string `json:"componentDefRef,omitempty"` - // References the name of a ComponentDefinition object. - // The ComponentDefinition specifies the behavior and characteristics of the Component. + // Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + // custom resource (CR) that defines the Component's characteristics and behavior. + // // If both `componentDefRef` and `componentDef` are provided, // the `componentDef` will take precedence over `componentDefRef`. // diff --git a/apis/apps/v1alpha1/clusterdefinition_types.go b/apis/apps/v1alpha1/clusterdefinition_types.go index 3612f577700..58b89233b3e 100644 --- a/apis/apps/v1alpha1/clusterdefinition_types.go +++ b/apis/apps/v1alpha1/clusterdefinition_types.go @@ -74,14 +74,15 @@ type ClusterTopologyComponent struct { // +kubebuilder:validation:Pattern:=`^[a-z]([a-z0-9\-]*[a-z0-9])?$` Name string `json:"name"` - // Specifies the name or prefix of the ComponentDefinition custom resource(CR) that - // defines the Component's characteristics and behavior. + // Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + // custom resource (CR) that defines the Component's characteristics and behavior. // - // When a prefix is used, the system selects the ComponentDefinition CR with the latest version that matches the prefix. + // The system selects the ComponentDefinition CR with the latest version that matches the pattern. // This approach allows: // // 1. Precise selection by providing the exact name of a ComponentDefinition CR. - // 2. Flexible and automatic selection of the most up-to-date ComponentDefinition CR by specifying a prefix. + // 2. Flexible and automatic selection of the most up-to-date ComponentDefinition CR + // by specifying a name prefix or regular expression pattern. // // Once set, this field cannot be updated. // diff --git a/apis/apps/v1alpha1/componentversion_types.go b/apis/apps/v1alpha1/componentversion_types.go index 33f8a0f145b..c9e6d674470 100644 --- a/apis/apps/v1alpha1/componentversion_types.go +++ b/apis/apps/v1alpha1/componentversion_types.go @@ -40,12 +40,13 @@ type ComponentVersionSpec struct { // ComponentVersionCompatibilityRule defines the compatibility between a set of component definitions and a set of releases. type ComponentVersionCompatibilityRule struct { // CompDefs specifies names for the component definitions associated with this ComponentVersion. - // Each name in the list can represent an exact name, or a name prefix. + // Each name in the list can represent an exact name, a name prefix, or a regular expression pattern. // // For example: // // - "mysql-8.0.30-v1alpha1": Matches the exact name "mysql-8.0.30-v1alpha1" // - "mysql-8.0.30": Matches all names starting with "mysql-8.0.30" + // - "^mysql-8.0.\d{1,2}$": Matches all names starting with "mysql-8.0." followed by one or two digits. // // +kubebuilder:validation:Required // +kubebuilder:validation:MinItems=1 diff --git a/apis/apps/v1alpha1/type.go b/apis/apps/v1alpha1/type.go index 886a47f20e1..e6dfa80e07f 100644 --- a/apis/apps/v1alpha1/type.go +++ b/apis/apps/v1alpha1/type.go @@ -1096,7 +1096,9 @@ type ClusterVars struct { // ClusterObjectReference defines information to let you locate the referenced object inside the same Cluster. type ClusterObjectReference struct { - // CompDef specifies the definition used by the component that the referent object resident in. + // Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + // custom resource (CR) used by the component that the referent object resident in. + // // If not specified, the component itself will be used. // // +optional diff --git a/config/crd/bases/apps.kubeblocks.io_clusterdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_clusterdefinitions.yaml index a626e4fb376..22c1da83248 100644 --- a/config/crd/bases/apps.kubeblocks.io_clusterdefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_clusterdefinitions.yaml @@ -85,20 +85,17 @@ spec: within a ClusterTopology. properties: compDef: - description: |- - Specifies the name or prefix of the ComponentDefinition custom resource(CR) that - defines the Component's characteristics and behavior. - - - When a prefix is used, the system selects the ComponentDefinition CR with the latest version that matches the prefix. - This approach allows: - - - 1. Precise selection by providing the exact name of a ComponentDefinition CR. - 2. Flexible and automatic selection of the most up-to-date ComponentDefinition CR by specifying a prefix. - - - Once set, this field cannot be updated. + description: "Specifies the exact name, name prefix, or + regular expression pattern for matching the name of + the ComponentDefinition\ncustom resource (CR) that defines + the Component's characteristics and behavior.\n\n\nThe + system selects the ComponentDefinition CR with the latest + version that matches the pattern.\nThis approach allows:\n\n\n1. + Precise selection by providing the exact name of a ComponentDefinition + CR.\n2. Flexible and automatic selection of the most + up-to-date ComponentDefinition CR\n\t by specifying + a name prefix or regular expression pattern.\n\n\nOnce + set, this field cannot be updated." maxLength: 64 type: string name: diff --git a/config/crd/bases/apps.kubeblocks.io_clusters.yaml b/config/crd/bases/apps.kubeblocks.io_clusters.yaml index e0766e1fe10..965355675fd 100644 --- a/config/crd/bases/apps.kubeblocks.io_clusters.yaml +++ b/config/crd/bases/apps.kubeblocks.io_clusters.yaml @@ -405,8 +405,10 @@ spec: type: object componentDef: description: |- - References the name of a ComponentDefinition object. - The ComponentDefinition specifies the behavior and characteristics of the Component. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) that defines the Component's characteristics and behavior. + + If both `componentDefRef` and `componentDef` are provided, the `componentDef` will take precedence over `componentDefRef`. maxLength: 64 @@ -9525,8 +9527,10 @@ spec: type: object componentDef: description: |- - References the name of a ComponentDefinition object. - The ComponentDefinition specifies the behavior and characteristics of the Component. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) that defines the Component's characteristics and behavior. + + If both `componentDefRef` and `componentDef` are provided, the `componentDef` will take precedence over `componentDefRef`. maxLength: 64 diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml index bd9064e8e9f..7ad3e446840 100644 --- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml @@ -12493,7 +12493,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string componentName: @@ -12644,7 +12647,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string multipleClusterObjectOption: @@ -12723,7 +12729,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string container: @@ -12832,7 +12841,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string endpoint: @@ -12932,7 +12944,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string host: diff --git a/config/crd/bases/apps.kubeblocks.io_componentversions.yaml b/config/crd/bases/apps.kubeblocks.io_componentversions.yaml index f5fbe385600..2a6f91047b8 100644 --- a/config/crd/bases/apps.kubeblocks.io_componentversions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_componentversions.yaml @@ -66,7 +66,7 @@ spec: compDefs: description: |- CompDefs specifies names for the component definitions associated with this ComponentVersion. - Each name in the list can represent an exact name, or a name prefix. + Each name in the list can represent an exact name, a name prefix, or a regular expression pattern. For example: @@ -74,6 +74,7 @@ spec: - "mysql-8.0.30-v1alpha1": Matches the exact name "mysql-8.0.30-v1alpha1" - "mysql-8.0.30": Matches all names starting with "mysql-8.0.30" + - "^mysql-8.0.\d{1,2}$": Matches all names starting with "mysql-8.0." followed by one or two digits. items: type: string maxItems: 128 diff --git a/controllers/apps/clusterdefinition_controller.go b/controllers/apps/clusterdefinition_controller.go index 7c604ec77da..27d6779f52f 100644 --- a/controllers/apps/clusterdefinition_controller.go +++ b/controllers/apps/clusterdefinition_controller.go @@ -35,6 +35,7 @@ import ( appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" appsconfig "github.com/apecloud/kubeblocks/controllers/apps/configuration" "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -190,6 +191,14 @@ func (r *ClusterDefinitionReconciler) validateTopology(rctx intctrlutil.RequestC return err } } + + // validate topology reference component definitions name pattern + for _, comp := range topology.Components { + if err := component.ValidateCompDefRegexp(comp.CompDef); err != nil { + return fmt.Errorf("invalid component definition reference pattern: %s", comp.CompDef) + } + } + compDefs, err := r.loadTopologyCompDefs(rctx.Ctx, topology) if err != nil { return err @@ -246,7 +255,7 @@ func (r *ClusterDefinitionReconciler) loadTopologyCompDefs(ctx context.Context, for _, comp := range topology.Components { defs := make([]*appsv1alpha1.ComponentDefinition, 0) for compDefName := range compDefs { - if strings.HasPrefix(compDefName, comp.CompDef) { + if component.CompDefMatched(compDefName, comp.CompDef) { defs = append(defs, compDefs[compDefName]) } } diff --git a/controllers/apps/componentdefinition_controller.go b/controllers/apps/componentdefinition_controller.go index 88b1f9caaac..7dd158be74c 100644 --- a/controllers/apps/componentdefinition_controller.go +++ b/controllers/apps/componentdefinition_controller.go @@ -27,6 +27,7 @@ import ( "reflect" "strings" + "github.com/pkg/errors" "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -40,6 +41,7 @@ import ( appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" appsconfig "github.com/apecloud/kubeblocks/controllers/apps/configuration" "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -211,6 +213,38 @@ func (r *ComponentDefinitionReconciler) validateRuntime(cli client.Client, rctx func (r *ComponentDefinitionReconciler) validateVars(cli client.Client, rctx intctrlutil.RequestCtx, cmpd *appsv1alpha1.ComponentDefinition) error { + if !checkUniqueItemWithValue(cmpd.Spec.Vars, "Name", nil) { + return fmt.Errorf("duplicate names of component vars are not allowed") + } + + // validate the reference to component definition name pattern + var compDef string + for _, cVar := range cmpd.Spec.Vars { + if cVar.ValueFrom == nil { + continue + } + switch { + case cVar.ValueFrom.HostNetworkVarRef != nil: + compDef = cVar.ValueFrom.HostNetworkVarRef.CompDef + case cVar.ValueFrom.ServiceVarRef != nil: + compDef = cVar.ValueFrom.ServiceVarRef.CompDef + case cVar.ValueFrom.ServiceRefVarRef != nil: + compDef = cVar.ValueFrom.ServiceRefVarRef.CompDef + case cVar.ValueFrom.ComponentVarRef != nil: + compDef = cVar.ValueFrom.ComponentVarRef.CompDef + case cVar.ValueFrom.CredentialVarRef != nil: + compDef = cVar.ValueFrom.CredentialVarRef.CompDef + default: + continue + } + + if len(compDef) == 0 { + continue + } + if err := component.ValidateCompDefRegexp(compDef); err != nil { + return errors.Wrapf(err, "invalid reference to component definition name pattern: %s", compDef) + } + } return nil } @@ -411,26 +445,26 @@ func getNCheckCompDefinition(ctx context.Context, cli client.Reader, name string return compDef, nil } -// listCompDefinitionsWithPrefix returns all component definitions whose names have prefix @namePrefix. -func listCompDefinitionsWithPrefix(ctx context.Context, cli client.Reader, namePrefix string) ([]*appsv1alpha1.ComponentDefinition, error) { +// listCompDefinitionsWithPattern returns all component definitions whose names match the given pattern (namePrefix or regular expression) +func listCompDefinitionsWithPattern(ctx context.Context, cli client.Reader, namePattern string) ([]*appsv1alpha1.ComponentDefinition, error) { compDefList := &appsv1alpha1.ComponentDefinitionList{} if err := cli.List(ctx, compDefList); err != nil { return nil, err } compDefsFullyMatched := make([]*appsv1alpha1.ComponentDefinition, 0) - compDefsPrefixMatched := make([]*appsv1alpha1.ComponentDefinition, 0) + compDefsPatternMatched := make([]*appsv1alpha1.ComponentDefinition, 0) for i, item := range compDefList.Items { - if item.Name == namePrefix { + if item.Name == namePattern { compDefsFullyMatched = append(compDefsFullyMatched, &compDefList.Items[i]) } - if strings.HasPrefix(item.Name, namePrefix) { - compDefsPrefixMatched = append(compDefsPrefixMatched, &compDefList.Items[i]) + if component.CompDefMatched(item.Name, namePattern) { + compDefsPatternMatched = append(compDefsPatternMatched, &compDefList.Items[i]) } } if len(compDefsFullyMatched) > 0 { return compDefsFullyMatched, nil } - return compDefsPrefixMatched, nil + return compDefsPatternMatched, nil } func checkUniqueItemWithValue(slice any, fieldName string, val any) bool { diff --git a/controllers/apps/componentdefinition_controller_test.go b/controllers/apps/componentdefinition_controller_test.go index 97916a3000b..05fdc44ed5d 100644 --- a/controllers/apps/componentdefinition_controller_test.go +++ b/controllers/apps/componentdefinition_controller_test.go @@ -336,6 +336,75 @@ var _ = Describe("ComponentDefinition Controller", func() { }) }) + Context("vars", func() { + It("ok", func() { + By("create a ComponentDefinition obj") + componentDefObj := testapps.NewComponentDefinitionFactory(componentDefName). + SetRuntime(nil). + AddVar(appsv1alpha1.EnvVar{ + Name: "VAR1", + Value: "value1", + }). + Create(&testCtx).GetObject() + + checkObjectStatus(componentDefObj, appsv1alpha1.AvailablePhase) + }) + + It("duplicate vars name", func() { + By("create a ComponentDefinition obj") + componentDefObj := testapps.NewComponentDefinitionFactory(componentDefName). + SetRuntime(nil). + AddVar(appsv1alpha1.EnvVar{ + Name: "VAR1", + Value: "value1", + }). + AddVar(appsv1alpha1.EnvVar{ + Name: "VAR1", + Value: "value2", + }). + Create(&testCtx).GetObject() + checkObjectStatus(componentDefObj, appsv1alpha1.UnavailablePhase) + }) + + It("valid var component definition name pattern", func() { + By("create a ComponentDefinition obj") + componentDefObj := testapps.NewComponentDefinitionFactory(componentDefName). + SetRuntime(nil). + AddVar(appsv1alpha1.EnvVar{ + Name: "VAR1", + ValueFrom: &appsv1alpha1.VarSource{ + ServiceRefVarRef: &appsv1alpha1.ServiceRefVarSelector{ + ClusterObjectReference: appsv1alpha1.ClusterObjectReference{ + Name: "service", + CompDef: "valid", + }, + }, + }, + }). + Create(&testCtx).GetObject() + checkObjectStatus(componentDefObj, appsv1alpha1.AvailablePhase) + }) + + It("invalid var component definition name pattern", func() { + By("create a ComponentDefinition obj") + componentDefObj := testapps.NewComponentDefinitionFactory(componentDefName). + SetRuntime(nil). + AddVar(appsv1alpha1.EnvVar{ + Name: "VAR1", + ValueFrom: &appsv1alpha1.VarSource{ + ServiceVarRef: &appsv1alpha1.ServiceVarSelector{ + ClusterObjectReference: appsv1alpha1.ClusterObjectReference{ + Name: "service", + CompDef: "(invalid", + }, + }, + }, + }). + Create(&testCtx).GetObject() + checkObjectStatus(componentDefObj, appsv1alpha1.UnavailablePhase) + }) + }) + Context("immutable", func() { newCmpdFn := func(processor func(*testapps.MockComponentDefinitionFactory)) *appsv1alpha1.ComponentDefinition { By("create a ComponentDefinition obj") diff --git a/controllers/apps/componentversion_controller.go b/controllers/apps/componentversion_controller.go index b78320dc8d1..2ff2f3f3bc7 100644 --- a/controllers/apps/componentversion_controller.go +++ b/controllers/apps/componentversion_controller.go @@ -26,6 +26,7 @@ import ( "slices" "strings" + "github.com/pkg/errors" "golang.org/x/exp/maps" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -141,6 +142,13 @@ func (r *ComponentVersionReconciler) reconcile(rctx intctrlutil.RequestCtx, // return intctrlutil.Reconciled() // } + if err = validateCompatibilityRulesCompDef(compVersion); err != nil { + if err1 := r.unavailable(r.Client, rctx, compVersion, err); err1 != nil { + return intctrlutil.CheckedRequeueWithError(err1, rctx.Log, "") + } + return intctrlutil.CheckedRequeueWithError(err, rctx.Log, "") + } + releaseToCompDefinitions, err := r.buildReleaseToCompDefinitionMapping(r.Client, rctx, compVersion) if err != nil { return intctrlutil.CheckedRequeueWithError(err, rctx.Log, "") @@ -178,7 +186,7 @@ func (r *ComponentVersionReconciler) buildReleaseToCompDefinitionMapping(cli cli continue } var err error - compDefs[compDef], err = listCompDefinitionsWithPrefix(rctx.Ctx, cli, compDef) + compDefs[compDef], err = listCompDefinitionsWithPattern(rctx.Ctx, cli, compDef) if err != nil { return nil, err } @@ -340,12 +348,24 @@ func (r *ComponentVersionReconciler) validateImages(release appsv1alpha1.Compone return nil } +// validateCompDef validates the reference component definition name pattern defined in compatibility rules. +func validateCompatibilityRulesCompDef(compVersion *appsv1alpha1.ComponentVersion) error { + for _, rule := range compVersion.Spec.CompatibilityRules { + for _, compDefName := range rule.CompDefs { + if err := component.ValidateCompDefRegexp(compDefName); err != nil { + return errors.Wrapf(err, "invalid reference to component definition name pattern: %s in compatibility rules", compDefName) + } + } + } + return nil +} + // resolveCompDefinitionNServiceVersion resolves and returns the specific component definition object and the service version supported. -func resolveCompDefinitionNServiceVersion(ctx context.Context, cli client.Reader, compDefName, serviceVersion string) (*appsv1alpha1.ComponentDefinition, string, error) { +func resolveCompDefinitionNServiceVersion(ctx context.Context, cli client.Reader, compDefNamePattern, serviceVersion string) (*appsv1alpha1.ComponentDefinition, string, error) { var ( compDef *appsv1alpha1.ComponentDefinition ) - compDefs, err := listCompDefinitionsWithPrefix(ctx, cli, compDefName) + compDefs, err := listCompDefinitionsWithPattern(ctx, cli, compDefNamePattern) if err != nil { return compDef, serviceVersion, err } @@ -368,7 +388,7 @@ func resolveCompDefinitionNServiceVersion(ctx context.Context, cli client.Reader // component definitions that support the service version compatibleCompDefs := serviceVersionToCompDefs[serviceVersion] if len(compatibleCompDefs) == 0 { - return compDef, serviceVersion, fmt.Errorf("no matched component definition found: %s", compDefName) + return compDef, serviceVersion, fmt.Errorf("no matched component definition found: %s", compDefNamePattern) } // choose the latest one diff --git a/controllers/apps/componentversion_controller_test.go b/controllers/apps/componentversion_controller_test.go index 16e1a063d4a..7afe2b8e589 100644 --- a/controllers/apps/componentversion_controller_test.go +++ b/controllers/apps/componentversion_controller_test.go @@ -235,6 +235,21 @@ var _ = Describe("ComponentVersion Controller", func() { })).Should(Succeed()) }) + It("update component definition with invalid regexp", func() { + By("update component version to reference an invalid regexp component definition") + compVersionKey := client.ObjectKeyFromObject(compVersionObj) + Eventually(testapps.GetAndChangeObj(&testCtx, compVersionKey, func(compVersion *appsv1alpha1.ComponentVersion) { + compVersion.Spec.CompatibilityRules[1].CompDefs = []string{testapps.CompDefName("(invalid-v3")} + })).Should(Succeed()) + + By("checking the object unavailable") + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(compVersionObj), + func(g Gomega, cmpv *appsv1alpha1.ComponentVersion) { + g.Expect(cmpv.Status.ObservedGeneration).Should(Equal(cmpv.GetGeneration())) + g.Expect(cmpv.Status.Phase).Should(Equal(appsv1alpha1.UnavailablePhase)) + })).Should(Succeed()) + }) + It("delete component definition", func() { By("update component version to delete definition v1.*") compVersionKey := client.ObjectKeyFromObject(compVersionObj) @@ -494,6 +509,66 @@ var _ = Describe("ComponentVersion Controller", func() { updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") }) + It("regular expression match definition", func() { + By("with definition exact regex and service version 1") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithExactRegex("v2.0"), testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + + By("with definition exact regex and service version v2") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithExactRegex("v2.0"), testapps.ServiceVersion("v2")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition exact regex and service version v3") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithExactRegex("v3.0"), testapps.ServiceVersion("v3")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") + + By("with definition v1 fuzzy regex and service version v0") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v1"), testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + + By("with definition v2 fuzzy regex and service version v1") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v2"), testapps.ServiceVersion("v2")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + }) + + It("regular expression match definition and w/o service version", func() { + By("with definition regex") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, "^"+testapps.CompDefinitionName, "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") + + By("with definition v1 regex") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v1"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition v2 regex") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v2"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + }) + It("match from definition", func() { By("with definition v1.0 and service version v0") compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), testapps.ServiceVersion("v0")) diff --git a/controllers/apps/transformer_cluster_load_resources.go b/controllers/apps/transformer_cluster_load_resources.go index a7c093281a4..f79a524f750 100644 --- a/controllers/apps/transformer_cluster_load_resources.go +++ b/controllers/apps/transformer_cluster_load_resources.go @@ -22,9 +22,11 @@ package apps import ( "fmt" + "github.com/pkg/errors" "k8s.io/apimachinery/pkg/types" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/graph" "github.com/apecloud/kubeblocks/pkg/generics" ) @@ -64,14 +66,27 @@ func (t *clusterLoadRefResourcesTransformer) Transform(ctx graph.TransformContex } func (t *clusterLoadRefResourcesTransformer) apiValidation(cluster *appsv1alpha1.Cluster) error { - if withClusterTopology(cluster) || - withClusterUserDefined(cluster) || - withClusterLegacyDefinition(cluster) || - withClusterSimplifiedAPI(cluster) { - return nil + if !withClusterTopology(cluster) && + !withClusterUserDefined(cluster) && + !withClusterLegacyDefinition(cluster) && + !withClusterSimplifiedAPI(cluster) { + return fmt.Errorf("cluster API validate error, clusterDef: %s, topology: %s, comps: %d, legacy comps: %d, simplified API: %v", + cluster.Spec.ClusterDefRef, cluster.Spec.Topology, clusterCompCnt(cluster), legacyClusterCompCnt(cluster), withClusterSimplifiedAPI(cluster)) + } + + for _, compSpec := range cluster.Spec.ComponentSpecs { + if err := validateCompDef(&compSpec); err != nil { + return err + } + } + + for _, shardingSpec := range cluster.Spec.ShardingSpecs { + if err := validateCompDef(&shardingSpec.Template); err != nil { + return err + } } - return fmt.Errorf("cluster API validate error, clusterDef: %s, topology: %s, comps: %d, legacy comps: %d, simplified API: %v", - cluster.Spec.ClusterDefRef, cluster.Spec.Topology, clusterCompCnt(cluster), legacyClusterCompCnt(cluster), withClusterSimplifiedAPI(cluster)) + + return nil } func (t *clusterLoadRefResourcesTransformer) checkNUpdateClusterTopology(transCtx *clusterTransformContext, cluster *appsv1alpha1.Cluster) error { @@ -95,6 +110,16 @@ func (t *clusterLoadRefResourcesTransformer) checkNUpdateClusterTopology(transCt return nil } +func validateCompDef(compSpec *appsv1alpha1.ClusterComponentSpec) error { + if len(compSpec.ComponentDef) == 0 { + return nil + } + if err := component.ValidateCompDefRegexp(compSpec.ComponentDef); err != nil { + return errors.Wrapf(err, "invalid reference component definition name pattern: %s", compSpec.ComponentDef) + } + return nil +} + func loadNCheckClusterDefinition(transCtx *clusterTransformContext, cluster *appsv1alpha1.Cluster) error { var cd *appsv1alpha1.ClusterDefinition if len(cluster.Spec.ClusterDefRef) > 0 { diff --git a/deploy/helm/crds/apps.kubeblocks.io_clusterdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_clusterdefinitions.yaml index a626e4fb376..22c1da83248 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_clusterdefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_clusterdefinitions.yaml @@ -85,20 +85,17 @@ spec: within a ClusterTopology. properties: compDef: - description: |- - Specifies the name or prefix of the ComponentDefinition custom resource(CR) that - defines the Component's characteristics and behavior. - - - When a prefix is used, the system selects the ComponentDefinition CR with the latest version that matches the prefix. - This approach allows: - - - 1. Precise selection by providing the exact name of a ComponentDefinition CR. - 2. Flexible and automatic selection of the most up-to-date ComponentDefinition CR by specifying a prefix. - - - Once set, this field cannot be updated. + description: "Specifies the exact name, name prefix, or + regular expression pattern for matching the name of + the ComponentDefinition\ncustom resource (CR) that defines + the Component's characteristics and behavior.\n\n\nThe + system selects the ComponentDefinition CR with the latest + version that matches the pattern.\nThis approach allows:\n\n\n1. + Precise selection by providing the exact name of a ComponentDefinition + CR.\n2. Flexible and automatic selection of the most + up-to-date ComponentDefinition CR\n\t by specifying + a name prefix or regular expression pattern.\n\n\nOnce + set, this field cannot be updated." maxLength: 64 type: string name: diff --git a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml index e0766e1fe10..965355675fd 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml @@ -405,8 +405,10 @@ spec: type: object componentDef: description: |- - References the name of a ComponentDefinition object. - The ComponentDefinition specifies the behavior and characteristics of the Component. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) that defines the Component's characteristics and behavior. + + If both `componentDefRef` and `componentDef` are provided, the `componentDef` will take precedence over `componentDefRef`. maxLength: 64 @@ -9525,8 +9527,10 @@ spec: type: object componentDef: description: |- - References the name of a ComponentDefinition object. - The ComponentDefinition specifies the behavior and characteristics of the Component. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) that defines the Component's characteristics and behavior. + + If both `componentDefRef` and `componentDef` are provided, the `componentDef` will take precedence over `componentDefRef`. maxLength: 64 diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml index bd9064e8e9f..7ad3e446840 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml @@ -12493,7 +12493,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string componentName: @@ -12644,7 +12647,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string multipleClusterObjectOption: @@ -12723,7 +12729,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string container: @@ -12832,7 +12841,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string endpoint: @@ -12932,7 +12944,10 @@ spec: properties: compDef: description: |- - CompDef specifies the definition used by the component that the referent object resident in. + Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition + custom resource (CR) used by the component that the referent object resident in. + + If not specified, the component itself will be used. type: string host: diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentversions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentversions.yaml index f5fbe385600..2a6f91047b8 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_componentversions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_componentversions.yaml @@ -66,7 +66,7 @@ spec: compDefs: description: |- CompDefs specifies names for the component definitions associated with this ComponentVersion. - Each name in the list can represent an exact name, or a name prefix. + Each name in the list can represent an exact name, a name prefix, or a regular expression pattern. For example: @@ -74,6 +74,7 @@ spec: - "mysql-8.0.30-v1alpha1": Matches the exact name "mysql-8.0.30-v1alpha1" - "mysql-8.0.30": Matches all names starting with "mysql-8.0.30" + - "^mysql-8.0.\d{1,2}$": Matches all names starting with "mysql-8.0." followed by one or two digits. items: type: string maxItems: 128 diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index f22e8d21d47..3d35db7aa3e 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -4030,9 +4030,9 @@ string
References the name of a ComponentDefinition object.
-The ComponentDefinition specifies the behavior and characteristics of the Component.
-If both componentDefRef
and componentDef
are provided,
+
Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition +custom resource (CR) that defines the Component’s characteristics and behavior.
+If both componentDefRef
and componentDef
are provided,
the componentDef
will take precedence over componentDefRef
.
CompDef specifies the definition used by the component that the referent object resident in. -If not specified, the component itself will be used.
+Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition +custom resource (CR) used by the component that the referent object resident in.
+If not specified, the component itself will be used.
Specifies the name or prefix of the ComponentDefinition custom resource(CR) that -defines the Component’s characteristics and behavior.
-When a prefix is used, the system selects the ComponentDefinition CR with the latest version that matches the prefix. +
Specifies the exact name, name prefix, or regular expression pattern for matching the name of the ComponentDefinition +custom resource (CR) that defines the Component’s characteristics and behavior.
+The system selects the ComponentDefinition CR with the latest version that matches the pattern. This approach allows:
Once set, this field cannot be updated.
CompDefs specifies names for the component definitions associated with this ComponentVersion. -Each name in the list can represent an exact name, or a name prefix.
+Each name in the list can represent an exact name, a name prefix, or a regular expression pattern.For example: