diff --git a/pkg/cluster/edit/diff.go b/pkg/cluster/edit/diff.go index 38db54514f..a8028b9fba 100644 --- a/pkg/cluster/edit/diff.go +++ b/pkg/cluster/edit/diff.go @@ -24,8 +24,9 @@ import ( ) const ( - validateTagName = "validate" - validateTagValue = "editable" + validateTagName = "validate" + validateTagEditable = "editable" + validateTagIgnore = "ignore" ) // ShowDiff write diff result into the Writer. @@ -60,22 +61,29 @@ func ValidateSpecDiff(s1, s2 interface{}) error { for _, c := range changelog { if len(c.Path) > 0 { // c.Path will be the tag value if TagName matched on the field - if c.Type == diff.UPDATE && c.Path[len(c.Path)-1] == validateTagValue { + if c.Type == diff.UPDATE && c.Path[len(c.Path)-1] == validateTagEditable { // If the field is marked as editable, it is allowed to be modified no matter // its parent level element is marked as editable or not continue } + pathEditable := true + pathIgnore := false for _, p := range c.Path { if _, err := strconv.Atoi(p); err == nil { // ignore slice offset counts continue } - if p != validateTagValue { + if p == validateTagIgnore { + pathIgnore = true + continue + } + if p != validateTagEditable { pathEditable = false } } - if pathEditable && (c.Type == diff.CREATE || c.Type == diff.DELETE) { + // if the path has any ignorable item, just ignore it + if pathIgnore || pathEditable && (c.Type == diff.CREATE || c.Type == diff.DELETE) { // If *every* parent elements on the path are all marked as editable, // AND the field itself is marked as editable, it is allowed to add or delete continue diff --git a/pkg/cluster/edit/diff_test.go b/pkg/cluster/edit/diff_test.go index 52b6b0a8b5..31304cbdf2 100644 --- a/pkg/cluster/edit/diff_test.go +++ b/pkg/cluster/edit/diff_test.go @@ -29,7 +29,7 @@ func TestDiff(t *testing.T) { type sampleDataMeta struct { IntSlice []int `yaml:"ints,omitempty"` StrSlice []string `yaml:"strs,omitempty" validate:"editable"` - MapSlice []map[string]interface{} `yaml:"maps,omitempty"` + MapSlice []map[string]interface{} `yaml:"maps,omitempty" validate:"ignore"` StrElem string `yaml:"stre" validate:"editable"` StructSlice1 []sampleDataElem `yaml:"slice1" validate:"editable"` StructSlice2 []sampleDataElem `yaml:"slice2,omitempty"` @@ -428,3 +428,54 @@ slice2: err = ValidateSpecDiff(d1, d2) c.Assert(err, IsNil) } + +func (d *diffSuite) TestValidateSpecDiff6(c *C) { + var d1 sampleDataMeta + var d2 sampleDataMeta + var err error + + err = yaml.Unmarshal([]byte(` +ints: [11, 12, 13] +maps: + - key0: 0 + - dot.key1: 1 + - dotkey.subkey.1: "1" +`), &d1) + c.Assert(err, IsNil) + + // Modify key without dot in name, in ignorable slice + err = yaml.Unmarshal([]byte(` +ints: [11, 12, 13] +maps: + - key0: 1 + - dot.key1: 1 + - dotkey.subkey.1: "1" +`), &d2) + c.Assert(err, IsNil) + err = ValidateSpecDiff(d1, d2) + c.Assert(err, IsNil) + + // Modify key with one dot in name, in ignorable slice + err = yaml.Unmarshal([]byte(` +ints: [11, 12, 13] +maps: + - key0: 0 + - dot.key1: 11 + - dotkey.subkey.1: "1" +`), &d2) + c.Assert(err, IsNil) + err = ValidateSpecDiff(d1, d2) + c.Assert(err, IsNil) + + // Modify key with two dots and number in name, in ignorable slice + err = yaml.Unmarshal([]byte(` +ints: [11, 12, 13] +maps: + - key0: 0 + - dot.key1: 1 + - dotkey.subkey.1: "12" +`), &d2) + c.Assert(err, IsNil) + err = ValidateSpecDiff(d1, d2) + c.Assert(err, IsNil) +} diff --git a/pkg/cluster/spec/cdc.go b/pkg/cluster/spec/cdc.go index 9ad66f82be..bb1138fa59 100644 --- a/pkg/cluster/spec/cdc.go +++ b/pkg/cluster/spec/cdc.go @@ -32,7 +32,7 @@ type CDCSpec struct { LogDir string `yaml:"log_dir,omitempty"` Offline bool `yaml:"offline,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` ResourceControl meta.ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` OS string `yaml:"os,omitempty"` diff --git a/pkg/cluster/spec/drainer.go b/pkg/cluster/spec/drainer.go index 5d1e59d7cd..f7b284764e 100644 --- a/pkg/cluster/spec/drainer.go +++ b/pkg/cluster/spec/drainer.go @@ -36,7 +36,7 @@ type DrainerSpec struct { CommitTS int64 `yaml:"commit_ts,omitempty"` Offline bool `yaml:"offline,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` ResourceControl meta.ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` OS string `yaml:"os,omitempty"` diff --git a/pkg/cluster/spec/pd.go b/pkg/cluster/spec/pd.go index 2b6e993bd6..411378e29a 100644 --- a/pkg/cluster/spec/pd.go +++ b/pkg/cluster/spec/pd.go @@ -41,7 +41,7 @@ type PDSpec struct { DataDir string `yaml:"data_dir,omitempty"` LogDir string `yaml:"log_dir,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` ResourceControl meta.ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` OS string `yaml:"os,omitempty"` diff --git a/pkg/cluster/spec/pump.go b/pkg/cluster/spec/pump.go index 3762daa25c..95dd6049b5 100644 --- a/pkg/cluster/spec/pump.go +++ b/pkg/cluster/spec/pump.go @@ -35,7 +35,7 @@ type PumpSpec struct { LogDir string `yaml:"log_dir,omitempty"` Offline bool `yaml:"offline,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` ResourceControl meta.ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` OS string `yaml:"os,omitempty"` diff --git a/pkg/cluster/spec/spec.go b/pkg/cluster/spec/spec.go index 0b655ac8da..ce6a4a22e7 100644 --- a/pkg/cluster/spec/spec.go +++ b/pkg/cluster/spec/spec.go @@ -78,21 +78,21 @@ type ( // ServerConfigs represents the server runtime configuration ServerConfigs struct { - TiDB map[string]interface{} `yaml:"tidb" validate:"editable"` - TiKV map[string]interface{} `yaml:"tikv" validate:"editable"` - PD map[string]interface{} `yaml:"pd" validate:"editable"` - TiFlash map[string]interface{} `yaml:"tiflash" validate:"editable"` - TiFlashLearner map[string]interface{} `yaml:"tiflash-learner" validate:"editable"` - Pump map[string]interface{} `yaml:"pump" validate:"editable"` - Drainer map[string]interface{} `yaml:"drainer" validate:"editable"` - CDC map[string]interface{} `yaml:"cdc" validate:"editable"` + TiDB map[string]interface{} `yaml:"tidb"` + TiKV map[string]interface{} `yaml:"tikv"` + PD map[string]interface{} `yaml:"pd"` + TiFlash map[string]interface{} `yaml:"tiflash"` + TiFlashLearner map[string]interface{} `yaml:"tiflash-learner"` + Pump map[string]interface{} `yaml:"pump"` + Drainer map[string]interface{} `yaml:"drainer"` + CDC map[string]interface{} `yaml:"cdc"` } // Specification represents the specification of topology.yaml Specification struct { GlobalOptions GlobalOptions `yaml:"global,omitempty" validate:"editable"` MonitoredOptions MonitoredOptions `yaml:"monitored,omitempty" validate:"editable"` - ServerConfigs ServerConfigs `yaml:"server_configs,omitempty" validate:"editable"` + ServerConfigs ServerConfigs `yaml:"server_configs,omitempty" validate:"ignore"` TiDBServers []TiDBSpec `yaml:"tidb_servers"` TiKVServers []TiKVSpec `yaml:"tikv_servers"` TiFlashServers []TiFlashSpec `yaml:"tiflash_servers"` diff --git a/pkg/cluster/spec/tidb.go b/pkg/cluster/spec/tidb.go index 339ffca5d2..4a5903b8ae 100644 --- a/pkg/cluster/spec/tidb.go +++ b/pkg/cluster/spec/tidb.go @@ -35,7 +35,7 @@ type TiDBSpec struct { DeployDir string `yaml:"deploy_dir,omitempty"` LogDir string `yaml:"log_dir,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` ResourceControl meta.ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` OS string `yaml:"os,omitempty"` diff --git a/pkg/cluster/spec/tiflash.go b/pkg/cluster/spec/tiflash.go index fa1e74f226..dc6ace100b 100644 --- a/pkg/cluster/spec/tiflash.go +++ b/pkg/cluster/spec/tiflash.go @@ -48,7 +48,7 @@ type TiFlashSpec struct { TmpDir string `yaml:"tmp_path,omitempty"` Offline bool `yaml:"offline,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` LearnerConfig map[string]interface{} `yaml:"learner_config,omitempty" validate:"editable"` ResourceControl meta.ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` diff --git a/pkg/cluster/spec/tikv.go b/pkg/cluster/spec/tikv.go index bc4f6742a4..3e8dfb71de 100644 --- a/pkg/cluster/spec/tikv.go +++ b/pkg/cluster/spec/tikv.go @@ -39,7 +39,7 @@ type TiKVSpec struct { LogDir string `yaml:"log_dir,omitempty"` Offline bool `yaml:"offline,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` ResourceControl meta.ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` OS string `yaml:"os,omitempty"` diff --git a/pkg/dm/spec/topology_dm.go b/pkg/dm/spec/topology_dm.go index b5b33eb22d..82352bd20a 100644 --- a/pkg/dm/spec/topology_dm.go +++ b/pkg/dm/spec/topology_dm.go @@ -122,7 +122,7 @@ type MasterSpec struct { DataDir string `yaml:"data_dir,omitempty"` LogDir string `yaml:"log_dir,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` ResourceControl ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` OS string `yaml:"os,omitempty"` @@ -183,7 +183,7 @@ type WorkerSpec struct { DataDir string `yaml:"data_dir,omitempty"` LogDir string `yaml:"log_dir,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` ResourceControl ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` OS string `yaml:"os,omitempty"` @@ -236,7 +236,7 @@ type PortalSpec struct { LogDir string `yaml:"log_dir,omitempty"` Timeout int `yaml:"timeout" default:"5"` NumaNode string `yaml:"numa_node,omitempty" validate:"editable"` - Config map[string]interface{} `yaml:"config,omitempty" validate:"editable"` + Config map[string]interface{} `yaml:"config,omitempty" validate:"ignore"` ResourceControl ResourceControl `yaml:"resource_control,omitempty" validate:"editable"` Arch string `yaml:"arch,omitempty"` OS string `yaml:"os,omitempty"`