Skip to content

Commit 75aef54

Browse files
committed
Feature: support exclude and intersection pattern for playbook label.
1 parent fc0a21c commit 75aef54

File tree

4 files changed

+195
-15
lines changed

4 files changed

+195
-15
lines changed

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,18 @@ GCFLAGS := "all=-N -l"
3333

3434
DEBUG_FLAGS := -gcflags=$(GCFLAGS)
3535

36+
# go test
37+
GO_TEST ?= $(GO) test
38+
3639
# test flags
40+
CASE ?= "."
41+
3742
TEST_FLAGS := -v
3843
TEST_FLAGS += -p 3
44+
TEST_FLAGS += -cover
45+
TEST_FLAGS += -count=1
46+
TEST_FLAGS += $(DEBUG_FLAGS)
47+
TEST_FLAGS += -run $(CASE)
3948

4049
# packages
4150
PACKAGES := $(PWD)/cmd/curveadm/main.go
@@ -50,7 +59,7 @@ debug:
5059
$(GOENV) $(GO) build -o $(OUTPUT) $(DEBUG_FLAGS) $(PACKAGES)
5160

5261
test:
53-
$(GO) test $(TEST_FLAGS) ./...
62+
$(GO_TEST) $(TEST_FLAGS) ./...
5463

5564
upload:
5665
@NOSCMD=$(NOSCMD) bash build/package/upload.sh $(VERSION)

cli/command/hosts/list.go

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
"github.com/opencurve/curveadm/internal/configure/hosts"
2828
"github.com/opencurve/curveadm/internal/tui"
2929
cliutil "github.com/opencurve/curveadm/internal/utils"
30-
utils "github.com/opencurve/curveadm/internal/utils"
3130
"github.com/spf13/cobra"
3231
)
3332

@@ -57,36 +56,107 @@ func NewListCommand(curveadm *cli.CurveAdm) *cobra.Command {
5756
return cmd
5857
}
5958

59+
/*
60+
* pattern description
61+
* --- ---
62+
* label2:label2 multiple labels: all hosts belong to label <label1> plus all hosts belong to <label2>
63+
* label2:!label2 excluding labels: all hosts belong to label <label1> except those belong to label <label2>
64+
* label2:&label2 intersection labels: any hosts belong to label <label1> that are also belong to label <label2>
65+
*/
66+
func parsePattern(labels []string) (include, exclude, intersect map[string]bool) {
67+
include = map[string]bool{}
68+
exclude = map[string]bool{}
69+
intersect = map[string]bool{}
70+
for _, label := range labels {
71+
if len(label) == 0 {
72+
continue
73+
}
74+
75+
switch label[0] {
76+
case '!':
77+
exclude[label[1:]] = true
78+
case '&':
79+
intersect[label[1:]] = true
80+
default:
81+
include[label] = true
82+
}
83+
}
84+
return
85+
}
86+
87+
// return true if dropped
88+
func excludeOne(hc *hosts.HostConfig, exclude map[string]bool) bool {
89+
if len(exclude) == 0 {
90+
return false
91+
}
92+
93+
for _, label := range hc.GetLabels() {
94+
if exclude[label] {
95+
return true
96+
}
97+
}
98+
return false
99+
}
100+
101+
// return true if selected
102+
func includeOne(hc *hosts.HostConfig, include map[string]bool) bool {
103+
if len(include) == 0 {
104+
return true
105+
}
106+
107+
for _, label := range hc.GetLabels() {
108+
if include[label] {
109+
return true
110+
}
111+
}
112+
return false
113+
}
114+
115+
// return true if selected
116+
func intersectOne(hc *hosts.HostConfig, intersect map[string]bool) bool {
117+
if len(intersect) == 0 {
118+
return true
119+
}
120+
121+
exist := map[string]bool{}
122+
for _, label := range hc.GetLabels() {
123+
if intersect[label] {
124+
exist[label] = true
125+
}
126+
}
127+
return len(exist) == len(intersect)
128+
}
129+
60130
func filter(data string, labels []string) ([]*hosts.HostConfig, error) {
61131
hcs, err := hosts.ParseHosts(data)
62132
if err != nil {
63133
return nil, err
64134
}
135+
if len(labels) == 0 {
136+
return hcs, nil
137+
}
65138

66139
out := []*hosts.HostConfig{}
67-
m := utils.Slice2Map(labels)
140+
include, exclude, intersect := parsePattern(labels)
68141
for _, hc := range hcs {
69-
if len(m) == 0 {
70-
out = append(out, hc)
142+
if excludeOne(hc, exclude) {
143+
continue
144+
} else if !includeOne(hc, include) {
145+
continue
146+
} else if !intersectOne(hc, intersect) {
71147
continue
72148
}
73-
for _, label := range hc.GetLabels() {
74-
if _, ok := m[label]; ok {
75-
out = append(out, hc)
76-
break
77-
}
78-
}
149+
out = append(out, hc)
79150
}
80-
81151
return out, nil
82152
}
83153

84154
func runList(curveadm *cli.CurveAdm, options listOptions) error {
85155
var hcs []*hosts.HostConfig
86156
var err error
87-
hosts := curveadm.Hosts()
88-
if len(hosts) > 0 {
89-
hcs, err = filter(hosts, options.labels) // filter hosts
157+
data := curveadm.Hosts()
158+
if len(data) > 0 {
159+
hcs, err = filter(data, options.labels) // filter hosts
90160
if err != nil {
91161
return err
92162
}

cli/command/hosts/list_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package hosts
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
const (
10+
data = `
11+
hosts:
12+
- host: host1
13+
hostname: 1.1.1.1
14+
labels:
15+
- all
16+
- host1
17+
- group1
18+
- host: host2
19+
hostname: 2.2.2.2
20+
labels:
21+
- all
22+
- host2
23+
- group1
24+
- group2
25+
- host: host3
26+
hostname: 3.3.3.3
27+
labels:
28+
- all
29+
- host3
30+
- group2
31+
`
32+
)
33+
34+
func run(t *testing.T, data string, labels []string, out []string) {
35+
assert := assert.New(t)
36+
hcs, err := filter(data, labels)
37+
assert.Nil(err)
38+
assert.Equal(len(hcs), len(out))
39+
for i, hc := range hcs {
40+
assert.Equal(hc.GetHost(), out[i])
41+
}
42+
}
43+
44+
func TestPlaybookLabel_ParsePattern(t *testing.T) {
45+
assert := assert.New(t)
46+
include, exclude, intersect := parsePattern([]string{"group1", "!group2", "&group3"})
47+
assert.Len(include, 1)
48+
assert.Len(exclude, 1)
49+
assert.Len(intersect, 1)
50+
assert.True(include["group1"])
51+
assert.True(exclude["group2"])
52+
assert.True(intersect["group3"])
53+
}
54+
55+
func TestPlaybookLabel_Basic(t *testing.T) {
56+
run(t, data, []string{"host1"}, []string{"host1"})
57+
run(t, data, []string{"host2"}, []string{"host2"})
58+
run(t, data, []string{"host3"}, []string{"host3"})
59+
run(t, data, []string{"group1"}, []string{"host1", "host2"})
60+
run(t, data, []string{"group2"}, []string{"host2", "host3"})
61+
run(t, data, []string{"all"}, []string{"host1", "host2", "host3"})
62+
}
63+
64+
func TestPlaybookLabel_EmptyLabel(t *testing.T) {
65+
run(t, data, []string{}, []string{"host1", "host2", "host3"})
66+
run(t, data, []string{"", ""}, []string{"host1", "host2", "host3"})
67+
}
68+
69+
func TestPlaybookLabel_MultiPattern(t *testing.T) {
70+
run(t, data, []string{"host1", "host2"}, []string{"host1", "host2"})
71+
run(t, data, []string{"host1", "host3"}, []string{"host1", "host3"})
72+
run(t, data, []string{"group1", "group2"}, []string{"host1", "host2", "host3"})
73+
}
74+
75+
func TestPlaybookLabel_ExcludePattern(t *testing.T) {
76+
run(t, data, []string{"group1", "!host1"}, []string{"host2"})
77+
run(t, data, []string{"group1", "!host2"}, []string{"host1"})
78+
run(t, data, []string{"group2", "!host1"}, []string{"host2", "host3"})
79+
run(t, data, []string{"group2", "!host2"}, []string{"host3"})
80+
run(t, data, []string{"group2", "!host3"}, []string{"host2"})
81+
run(t, data, []string{"!host1"}, []string{"host2", "host3"})
82+
run(t, data, []string{"!group1"}, []string{"host3"})
83+
run(t, data, []string{"!all"}, []string{})
84+
}
85+
86+
func TestPlaybookLabel_IntersectPattern(t *testing.T) {
87+
run(t, data, []string{"group1", "&host1"}, []string{"host1"})
88+
run(t, data, []string{"group1", "&host2"}, []string{"host2"})
89+
run(t, data, []string{"group1", "&host3"}, []string{})
90+
run(t, data, []string{"all", "&group1"}, []string{"host1", "host2"})
91+
run(t, data, []string{"all", "&group2"}, []string{"host2", "host3"})
92+
run(t, data, []string{"all", "&group1", "&group2"}, []string{"host2"})
93+
run(t, data, []string{"&group1", "&group2"}, []string{"host2"})
94+
run(t, data, []string{"host2", "&group1", "host3"}, []string{"host2"})
95+
}
96+
97+
func TestPlaybookLabel_MixPattern(t *testing.T) {
98+
run(t, data, []string{"&group1", "&group2"}, []string{"host2"})
99+
run(t, data, []string{"&group1", "&group2", "!host2"}, []string{})
100+
}

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,6 +1465,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
14651465
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
14661466
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
14671467
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
1468+
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
14681469
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
14691470
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
14701471
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=

0 commit comments

Comments
 (0)