Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support upate parameters in non-default sections, e.g client or mysql: --set mysql.default-character-set = utf8mb4 (#6453) #6634

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apis/apps/v1alpha1/configconstraint_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,15 @@ type IniConfig struct {
// sectionName describes ini section.
// +optional
SectionName string `json:"sectionName,omitempty"`

// applyAllSection determines whether to support multiple sections in the ini file.
// if set to true, all sections parameter can be updated, e.g: client.default_character_set=utf8mb4
// +optional
ApplyAllSection *bool `json:"applyAllSection,omitempty"`

// parametersInSection is a map of section name to parameters.
// +optional
ParametersInSectionAsMap map[string][]string `json:"parametersInSection,omitempty"`
}

// +genclient
Expand Down Expand Up @@ -309,3 +318,10 @@ type ConfigConstraintList struct {
func init() {
SchemeBuilder.Register(&ConfigConstraint{}, &ConfigConstraintList{})
}

func (in *IniConfig) IsSupportMultiSection() bool {
if in.ApplyAllSection == nil {
return false
}
return *in.ApplyAllSection
}
22 changes: 21 additions & 1 deletion apis/apps/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions config/crd/bases/apps.kubeblocks.io_configconstraints.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@ spec:
iniConfig:
description: iniConfig represents the ini options.
properties:
applyAllSection:
description: 'applyAllSection determines whether to support
multiple sections in the ini file. if set to true, all sections
parameter can be updated, e.g: client.default_character_set=utf8mb4'
type: boolean
parametersInSection:
additionalProperties:
items:
type: string
type: array
description: parametersInSection is a map of section name
to parameters.
type: object
sectionName:
description: sectionName describes ini section.
type: string
Expand Down
13 changes: 13 additions & 0 deletions deploy/helm/crds/apps.kubeblocks.io_configconstraints.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@ spec:
iniConfig:
description: iniConfig represents the ini options.
properties:
applyAllSection:
description: 'applyAllSection determines whether to support
multiple sections in the ini file. if set to true, all sections
parameter can be updated, e.g: client.default_character_set=utf8mb4'
type: boolean
parametersInSection:
additionalProperties:
items:
type: string
type: array
description: parametersInSection is a map of section name
to parameters.
type: object
sectionName:
description: sectionName describes ini section.
type: string
Expand Down
25 changes: 25 additions & 0 deletions docs/developer_docs/api-reference/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -10254,6 +10254,31 @@ string
<p>sectionName describes ini section.</p>
</td>
</tr>
<tr>
<td>
<code>applyAllSection</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>applyAllSection determines whether to support multiple sections in the ini file.
if set to true, all sections parameter can be updated, e.g: client.default_character_set=utf8mb4</p>
</td>
</tr>
<tr>
<td>
<code>parametersInSection</code><br/>
<em>
map[string][]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>parametersInSection is a map of section name to parameters.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="apps.kubeblocks.io/v1alpha1.Issuer">Issuer
Expand Down
100 changes: 80 additions & 20 deletions pkg/configuration/core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package core
import (
"encoding/json"
"path"
"slices"
"strings"

"github.com/StudioSol/set"
Expand Down Expand Up @@ -233,15 +234,22 @@ func WithFormatterConfig(formatConfig *appsv1alpha1.FormatterConfig) Option {
return func(ctx *CfgOpOption) {
if formatConfig.Format == appsv1alpha1.Ini && formatConfig.IniConfig != nil {
ctx.IniContext = &IniContext{
SectionName: formatConfig.IniConfig.SectionName,
SectionName: formatConfig.IniConfig.SectionName,
ParametersInSectionAsMap: formatConfig.IniConfig.ParametersInSectionAsMap,
ApplyAllSection: false,
}
if formatConfig.IniConfig.ApplyAllSection != nil {
ctx.IniContext.ApplyAllSection = *formatConfig.IniConfig.ApplyAllSection
}
}
}
}

func NestedPrefixField(formatConfig *appsv1alpha1.FormatterConfig) string {
if formatConfig != nil && formatConfig.Format == appsv1alpha1.Ini && formatConfig.IniConfig != nil {
return formatConfig.IniConfig.SectionName
if !formatConfig.IniConfig.IsSupportMultiSection() {
return formatConfig.IniConfig.SectionName
}
}
return ""
}
Expand Down Expand Up @@ -276,7 +284,7 @@ func (c *cfgWrapper) queryAllCfg(jsonpath string, option CfgOpOption) ([]byte, e
return util.RetrievalWithJSONPath(tops, jsonpath)
}

func (c cfgWrapper) getConfigObject(option CfgOpOption) unstructured.ConfigObject {
func (c *cfgWrapper) getConfigObject(option CfgOpOption) unstructured.ConfigObject {
if len(c.v) == 0 {
return nil
}
Expand All @@ -289,13 +297,28 @@ func (c cfgWrapper) getConfigObject(option CfgOpOption) unstructured.ConfigObjec
}

func (c *cfgWrapper) generateKey(paramKey string, option CfgOpOption) string {
if option.IniContext != nil && len(option.IniContext.SectionName) > 0 {
return strings.Join([]string{option.IniContext.SectionName, paramKey}, unstructured.DelimiterDot)
if option.IniContext != nil {
// support special section, e.g: mysql.default-character-set
if strings.Index(paramKey, unstructured.DelimiterDot) > 0 {
return paramKey
}
sectionName := fromIniConfig(paramKey, option.IniContext)
if sectionName != "" {
return strings.Join([]string{sectionName, paramKey}, unstructured.DelimiterDot)
}
}

return paramKey
}

func fromIniConfig(paramKey string, iniContext *IniContext) string {
for s, params := range iniContext.ParametersInSectionAsMap {
if slices.Contains(params, paramKey) {
return s
}
}
return iniContext.SectionName
}

func FromCMKeysSelector(keys []string) *set.LinkedHashSetString {
var cmKeySet *set.LinkedHashSetString
if len(keys) > 0 {
Expand All @@ -305,20 +328,35 @@ func FromCMKeysSelector(keys []string) *set.LinkedHashSetString {
}

func GenerateVisualizedParamsList(configPatch *ConfigPatchInfo, formatConfig *appsv1alpha1.FormatterConfig, sets *set.LinkedHashSetString) []VisualizedParam {
return GenerateVisualizedParamsListImpl(configPatch, formatConfig, sets, false)
}

// GenerateVisualizedParamsListImpl Generate visualized parameters list
// for kbcli edit-config command
func GenerateVisualizedParamsListImpl(configPatch *ConfigPatchInfo, formatConfig *appsv1alpha1.FormatterConfig, sets *set.LinkedHashSetString, force bool) []VisualizedParam {
if !configPatch.IsModify {
return nil
}

var trimPrefix = NestedPrefixField(formatConfig)
var applyAllSections = isIniCfgAndSupportMultiSection(formatConfig) || force
var section = getIniSection(formatConfig)

r := make([]VisualizedParam, 0)
r = append(r, generateUpdateParam(configPatch.UpdateConfig, trimPrefix, sets)...)
r = append(r, generateUpdateKeyParam(configPatch.AddConfig, trimPrefix, AddedType, sets)...)
r = append(r, generateUpdateKeyParam(configPatch.DeleteConfig, trimPrefix, DeletedType, sets)...)
r = append(r, generateUpdateParam(configPatch.UpdateConfig, trimPrefix, sets, applyAllSections, section)...)
r = append(r, generateUpdateKeyParam(configPatch.AddConfig, trimPrefix, AddedType, sets, applyAllSections, section)...)
r = append(r, generateUpdateKeyParam(configPatch.DeleteConfig, trimPrefix, DeletedType, sets, applyAllSections, section)...)
return r
}

func generateUpdateParam(updatedParams map[string][]byte, trimPrefix string, sets *set.LinkedHashSetString) []VisualizedParam {
func getIniSection(config *appsv1alpha1.FormatterConfig) string {
if config != nil && config.IniConfig != nil {
return config.IniConfig.SectionName
}
return ""
}

func generateUpdateParam(updatedParams map[string][]byte, trimPrefix string, sets *set.LinkedHashSetString, applyAllSections bool, defaultSection string) []VisualizedParam {
r := make([]VisualizedParam, 0, len(updatedParams))

for key, b := range updatedParams {
Expand All @@ -330,7 +368,7 @@ func generateUpdateParam(updatedParams map[string][]byte, trimPrefix string, set
if err := json.Unmarshal(b, &v); err != nil {
return nil
}
if params := checkAndFlattenMap(v, trimPrefix); params != nil {
if params := checkAndFlattenMap(v, trimPrefix, applyAllSections, defaultSection); params != nil {
r = append(r, VisualizedParam{
Key: key,
Parameters: params,
Expand All @@ -341,18 +379,18 @@ func generateUpdateParam(updatedParams map[string][]byte, trimPrefix string, set
return r
}

func checkAndFlattenMap(v any, trim string) []ParameterPair {
func checkAndFlattenMap(v any, trim string, applyAllSections bool, defaultSection string) []ParameterPair {
m := cast.ToStringMap(v)
if m != nil && trim != "" {
if m != nil && !applyAllSections && trim != "" {
m = cast.ToStringMap(m[trim])
}
if m != nil {
return flattenMap(m, "")
return flattenMap(m, "", applyAllSections, defaultSection)
}
return nil
}

func flattenMap(m map[string]interface{}, prefix string) []ParameterPair {
func flattenMap(m map[string]interface{}, prefix string, applyAllSections bool, defaultSection string) []ParameterPair {
if prefix != "" {
prefix += unstructured.DelimiterDot
}
Expand All @@ -362,10 +400,10 @@ func flattenMap(m map[string]interface{}, prefix string) []ParameterPair {
fullKey := prefix + k
switch m2 := val.(type) {
case map[string]interface{}:
r = append(r, flattenMap(m2, fullKey)...)
r = append(r, flattenMap(m2, fullKey, applyAllSections, defaultSection)...)
case []interface{}:
r = append(r, ParameterPair{
Key: transArrayFieldName(fullKey),
Key: transArrayFieldName(trimPrimaryKeyName(fullKey, applyAllSections, defaultSection)),
Value: util.ToPointer(transJSONString(val)),
})
default:
Expand All @@ -374,22 +412,44 @@ func flattenMap(m map[string]interface{}, prefix string) []ParameterPair {
v = util.ToPointer(cast.ToString(val))
}
r = append(r, ParameterPair{
Key: fullKey,
Key: trimPrimaryKeyName(fullKey, applyAllSections, defaultSection),
Value: v,
})
}
}
return r
}

func generateUpdateKeyParam(files map[string]interface{}, trimPrefix string, updatedType ParameterUpdateType, sets *set.LinkedHashSetString) []VisualizedParam {
func trimPrimaryKeyName(key string, applyAllSections bool, defaultSection string) string {
if !applyAllSections || defaultSection == "" {
return key
}

pos := strings.Index(key, ".")
switch {
case pos < 0:
return key
case key[:pos] == defaultSection:
return key[pos+1:]
default:
return key
}
}

func generateUpdateKeyParam(
files map[string]interface{},
trimPrefix string,
updatedType ParameterUpdateType,
sets *set.LinkedHashSetString,
applyAllSections bool,
defaultSection string) []VisualizedParam {
r := make([]VisualizedParam, 0, len(files))

for key, params := range files {
if sets != nil && sets.Length() > 0 && !sets.InArray(key) {
continue
}
if params := checkAndFlattenMap(params, trimPrefix); params != nil {
if params := checkAndFlattenMap(params, trimPrefix, applyAllSections, defaultSection); params != nil {
r = append(r, VisualizedParam{
Key: key,
Parameters: params,
Expand Down
27 changes: 27 additions & 0 deletions pkg/configuration/core/config_patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,33 @@ func TestConfigPatch(t *testing.T) {
log.Log.Info("patch : %v", patch)
require.False(t, patch.IsModify)
}

{

require.Nil(t,
cfg.MergeFrom(map[string]interface{}{
"server-id": 1,
"socket": "/data/mysql/tmp/mysqld.sock2",
"client.port": "6666",
}, NewCfgOptions("", func(ctx *CfgOpOption) {
// filter mysqld
ctx.IniContext = &IniContext{
SectionName: "mysqld",
ParametersInSectionAsMap: map[string][]string{
"client": {"port", "socket"},
},
}
})))
content, err := cfg.ToCfgContent()
require.Nil(t, err)
newContent := content[cfg.name]
// CreateMergePatch([]byte(iniConfig), []byte(newContent), cfg.Option)
patch, err := CreateMergePatch([]byte(iniConfig), []byte(newContent), cfg.Option)
require.Nil(t, err)
log.Log.Info("patch : %v", patch)
require.True(t, patch.IsModify)
require.Equal(t, string(patch.UpdateConfig["raw"]), `{"client":{"port":"6666","socket":"/data/mysql/tmp/mysqld.sock2"}}`)
}
}

func TestYamlConfigPatch(t *testing.T) {
Expand Down
Loading
Loading