Skip to content

Commit

Permalink
feat: compDef reference support regular expression matching (#8094)
Browse files Browse the repository at this point in the history
  • Loading branch information
Y-Rookie committed Sep 10, 2024
1 parent efa1b5f commit c69f94f
Show file tree
Hide file tree
Showing 24 changed files with 582 additions and 92 deletions.
5 changes: 3 additions & 2 deletions apis/apps/v1alpha1/cluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
//
Expand Down
9 changes: 5 additions & 4 deletions apis/apps/v1alpha1/clusterdefinition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
3 changes: 2 additions & 1 deletion apis/apps/v1alpha1/componentversion_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion apis/apps/v1alpha1/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 11 additions & 14 deletions config/crd/bases/apps.kubeblocks.io_clusterdefinitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 8 additions & 4 deletions config/crd/bases/apps.kubeblocks.io_clusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
25 changes: 20 additions & 5 deletions config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion config/crd/bases/apps.kubeblocks.io_componentversions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ 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:
- "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
Expand Down
11 changes: 10 additions & 1 deletion controllers/apps/clusterdefinition_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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])
}
}
Expand Down
48 changes: 41 additions & 7 deletions controllers/apps/componentdefinition_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
)

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down
69 changes: 69 additions & 0 deletions controllers/apps/componentdefinition_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading

0 comments on commit c69f94f

Please sign in to comment.