Repository: kubeclipper/kubeclipper (go)

Issue:
[create cluster] masters and workers can specify same node
<!--
You don't need to remove this comment section, it's invisible on the issues page.

## General remarks

* Attention, please fill out this issues form using English!
* 注意！GitHub Issue 请使用英文
-->

**Describe the Bug**
masters and workers can specify same node, we need add a check

**Versions Used**
Kubernetes: 1.23.17
Kubeclipper: master


**Environment**
How many nodes and their hardware configuration:

For example: CentOS 7.5 / 3 masters:  8cpu/8g; 3 nodes: 8cpu/16g
(and other info are welcomed to help us debugging)

**How To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

Pull request description:
fix: create cluster add node conflit check
<!-- Thanks for sending a pull request! Here are some tips for you:

1. If you want **faster** PR reviews, read how: https://github.com/kubeclipper/community/blob/master/developer-guide/development/the-pr-author-guide-to-getting-through-code-review.md
2. In case you want to know how your PR got reviewed, read: https://github.com/kubeclipper/community/blob/master/developer-guide/development/code-review-guide.md
3. Here are some coding convetions followed by Kubeclipper community: https://github.com/kubeclipper/community/blob/master/developer-guide/development/coding-conventions.md
-->

### What type of PR is this?
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->
/kind bug

### What this PR does / why we need it:

### Which issue(s) this PR fixes:
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #817

### Special notes for reviewers:
```
```

### Does this PR introduced a user-facing change?
<!--
If no, just write "None" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md
-->
```release-note
None
```

### Additional documentation, usage docs, etc.:
<!--
This section can be blank if this pull request does not require a release note.
Please use the following format for linking documentation or pass the
section below:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs
None
```

Patch:
diff --git a/pkg/cli/create/create_cluster.go b/pkg/cli/create/create_cluster.go
index 711381e..922ab4b 100644
--- a/pkg/cli/create/create_cluster.go
+++ b/pkg/cli/create/create_cluster.go
@@ -354,6 +354,11 @@ func (l *CreateClusterOptions) ValidateArgs(cmd *cobra.Command) error {
 		l.createdByIP = true
 	}
 
+	// check is masters and workers conflict
+	if hasConflict(l.Masters, l.Workers) {
+		return errors.New("master and worker conflict")
+	}
+
 	if l.CaCertFile != "" || l.CaKeyFile != "" {
 		caCert, err := os.ReadFile(l.CaCertFile)
 		if err != nil {
diff --git a/pkg/cli/create/utils.go b/pkg/cli/create/utils.go
index 3fb17d9..99f25cc 100644
--- a/pkg/cli/create/utils.go
+++ b/pkg/cli/create/utils.go
@@ -47,3 +47,10 @@ func getComponentAvailableVersions(client *kc.Client, offline bool, component st
 	}
 	return set
 }
+
+// hasConflict 判断是否有重复的节点
+func hasConflict(masters, workers []string) bool {
+	set := sets.NewString(masters...)
+	set.Insert(workers...)
+	return set.Len() != len(masters)+len(workers)
+}


Test patch:
diff --git a/pkg/cli/create/utils_test.go b/pkg/cli/create/utils_test.go
new file mode 100644
index 0000000..0cf1a87
--- /dev/null
+++ b/pkg/cli/create/utils_test.go
@@ -0,0 +1,76 @@
+package create
+
+import "testing"
+
+func Test_hasConflict(t *testing.T) {
+	tests := []struct {
+		name     string
+		masters  []string
+		workers  []string
+		expected bool
+	}{
+		{
+			name:     "no conflict, both masters and workers are empty",
+			masters:  []string{},
+			workers:  []string{},
+			expected: false, // 无冲突
+		},
+		{
+			name:     "no conflict, masters and workers are different IPs",
+			masters:  []string{"192.168.1.1", "192.168.1.2"},
+			workers:  []string{"192.168.2.1", "192.168.2.2"},
+			expected: false, // 无冲突
+		},
+		{
+			name:     "conflict, one IP in both masters and workers",
+			masters:  []string{"192.168.1.1"},
+			workers:  []string{"192.168.1.1"}, // 有重复 IP
+			expected: true,                    // 有冲突
+		},
+		{
+			name:     "no conflict, masters and workers have distinct IPs",
+			masters:  []string{"192.168.1.1", "192.168.1.2"},
+			workers:  []string{"192.168.2.1", "192.168.2.2"},
+			expected: false, // 无冲突
+		},
+		{
+			name:     "conflict, one element each, same IP",
+			masters:  []string{"192.168.1.1"},
+			workers:  []string{"192.168.1.1"}, // 冲突
+			expected: true,                    // 有冲突
+		},
+		{
+			name:     "conflict, multiple IPs with overlap",
+			masters:  []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"},
+			workers:  []string{"192.168.2.1", "192.168.1.2", "192.168.2.2"}, // `192.168.1.2` 与 workers 有重复
+			expected: true,                                                  // 有冲突
+		},
+		{
+			name:     "no conflict, workers are distinct IPs",
+			masters:  []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"},
+			workers:  []string{"192.168.2.1", "192.168.2.2", "192.168.2.3"}, // 没有重复
+			expected: false,                                                 // 无冲突
+		},
+		{
+			name:     "no conflict, masters only",
+			masters:  []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"},
+			workers:  []string{}, // 只有 masters，workers 为空
+			expected: false,      // 无冲突
+		},
+		{
+			name:     "no conflict, workers only",
+			masters:  []string{},
+			workers:  []string{"192.168.2.1", "192.168.2.2", "192.168.2.3"}, // 只有 workers，masters 为空
+			expected: false,                                                 // 无冲突
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := hasConflict(tt.masters, tt.workers)
+			if result != tt.expected {
+				t.Errorf("hasConflict(%v, %v) = %v; want %v", tt.masters, tt.workers, result, tt.expected)
+			}
+		})
+	}
+}