Skip to content

Commit 1c5be10

Browse files
ip allow list helper improvements (#456)
- Moves helpers out of kv files and to ipallowlist.go. (Still in same `text` package, so real refactor here, just moving stuff around). - Improves `ipAllowListEquals` to be order-insensitive. If the server returns the same allow list entries in different orders on different requests, our equality check will still say they are equal. Pulled this out of [GROW-2120: PG Update](https://linear.app/render-com/issue/GROW-2120/pg-update) GitOrigin-RevId: 5da8bcd8d8a6c4dc19b84b72fc0ca68f8ebbfe42
1 parent b22065b commit 1c5be10

3 files changed

Lines changed: 110 additions & 42 deletions

File tree

pkg/text/ipallowlist.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package text
2+
3+
import (
4+
"cmp"
5+
"fmt"
6+
"slices"
7+
"strings"
8+
9+
"github.com/render-oss/cli/pkg/client"
10+
)
11+
12+
// Shared IP allow-list rendering helpers, used by both Key Value and Postgres
13+
// text output. The parsing counterpart lives in pkg/types/ipallowlist.go.
14+
15+
// ipAllowListBlock renders the allow-list as either a one-liner ("IP allow-list: (empty)")
16+
// or a header line followed by indented entries — each " - <cidr> (<description>)" or
17+
// " - <cidr>" when description is empty.
18+
func ipAllowListBlock(entries []client.CidrBlockAndDescription) string {
19+
if len(entries) == 0 {
20+
return "IP allow-list: (empty)"
21+
}
22+
var b strings.Builder
23+
b.WriteString("IP allow-list:")
24+
for _, e := range entries {
25+
if e.Description != "" {
26+
fmt.Fprintf(&b, "\n - %s (%s)", e.CidrBlock, e.Description)
27+
} else {
28+
fmt.Fprintf(&b, "\n - %s", e.CidrBlock)
29+
}
30+
}
31+
return b.String()
32+
}
33+
34+
// ipAllowListLabel renders a compact count label for use in update diffs.
35+
func ipAllowListLabel(entries []client.CidrBlockAndDescription) string {
36+
switch len(entries) {
37+
case 0:
38+
return "(empty)"
39+
case 1:
40+
return "1 entry"
41+
default:
42+
return fmt.Sprintf("%d entries", len(entries))
43+
}
44+
}
45+
46+
// ipAllowListEqual reports whether two allow-lists contain the same entries.
47+
// An allow-list is conceptually a set, so the comparison is order-insensitive:
48+
// it sorts copies (never the caller's slices, which are live server data)
49+
// before comparing, so a reordered list is not reported as a change.
50+
func ipAllowListEqual(a, b []client.CidrBlockAndDescription) bool {
51+
if len(a) != len(b) {
52+
return false
53+
}
54+
as := slices.Clone(a)
55+
bs := slices.Clone(b)
56+
byKey := func(x, y client.CidrBlockAndDescription) int {
57+
return cmp.Or(
58+
cmp.Compare(x.CidrBlock, y.CidrBlock),
59+
cmp.Compare(x.Description, y.Description),
60+
)
61+
}
62+
slices.SortFunc(as, byKey)
63+
slices.SortFunc(bs, byKey)
64+
return slices.Equal(as, bs)
65+
}

pkg/text/ipallowlist_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package text
2+
3+
import (
4+
"testing"
5+
6+
"github.com/render-oss/cli/pkg/client"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestIPAllowListEqual(t *testing.T) {
11+
office := client.CidrBlockAndDescription{CidrBlock: "203.0.113.5/32", Description: "office"}
12+
internal := client.CidrBlockAndDescription{CidrBlock: "10.0.0.0/8", Description: "internal"}
13+
14+
t.Run("same entries, same order", func(t *testing.T) {
15+
a := []client.CidrBlockAndDescription{office, internal}
16+
b := []client.CidrBlockAndDescription{office, internal}
17+
assert.True(t, ipAllowListEqual(a, b))
18+
})
19+
20+
t.Run("same entries, different order", func(t *testing.T) {
21+
a := []client.CidrBlockAndDescription{office, internal}
22+
b := []client.CidrBlockAndDescription{internal, office}
23+
assert.True(t, ipAllowListEqual(a, b))
24+
})
25+
26+
t.Run("different entries", func(t *testing.T) {
27+
a := []client.CidrBlockAndDescription{office}
28+
b := []client.CidrBlockAndDescription{internal}
29+
assert.False(t, ipAllowListEqual(a, b))
30+
})
31+
32+
t.Run("different lengths", func(t *testing.T) {
33+
a := []client.CidrBlockAndDescription{office, internal}
34+
b := []client.CidrBlockAndDescription{office}
35+
assert.False(t, ipAllowListEqual(a, b))
36+
})
37+
38+
t.Run("does not mutate the caller's slices", func(t *testing.T) {
39+
a := []client.CidrBlockAndDescription{office, internal}
40+
b := []client.CidrBlockAndDescription{internal, office}
41+
ipAllowListEqual(a, b)
42+
assert.Equal(t, []client.CidrBlockAndDescription{office, internal}, a)
43+
assert.Equal(t, []client.CidrBlockAndDescription{internal, office}, b)
44+
})
45+
}

pkg/text/keyvalue.go

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,6 @@ func KeyValueDetail(kv *client.KeyValueDetail) string {
4545
return strings.Join(lines, "\n")
4646
}
4747

48-
// ipAllowListBlock renders the allow-list as either a one-liner ("IP allow-list: (empty)")
49-
// or a header line followed by indented entries — each " - <cidr> (<description>)" or
50-
// " - <cidr>" when description is empty.
51-
func ipAllowListBlock(entries []client.CidrBlockAndDescription) string {
52-
if len(entries) == 0 {
53-
return "IP allow-list: (empty)"
54-
}
55-
var b strings.Builder
56-
b.WriteString("IP allow-list:")
57-
for _, e := range entries {
58-
if e.Description != "" {
59-
fmt.Fprintf(&b, "\n - %s (%s)", e.CidrBlock, e.Description)
60-
} else {
61-
fmt.Fprintf(&b, "\n - %s", e.CidrBlock)
62-
}
63-
}
64-
return b.String()
65-
}
66-
6748
func KeyValueGetDetail(kv *client.KeyValueDetail, conn *client.KeyValueConnectionInfo) string {
6849
detail := KeyValueDetail(kv)
6950
if len(kv.IpAllowList) == 0 {
@@ -111,26 +92,3 @@ func memoryPolicyLabel(kv *client.KeyValueDetail) string {
11192
}
11293
return *kv.Options.MaxmemoryPolicy
11394
}
114-
115-
func ipAllowListLabel(entries []client.CidrBlockAndDescription) string {
116-
switch len(entries) {
117-
case 0:
118-
return "(empty)"
119-
case 1:
120-
return "1 entry"
121-
default:
122-
return fmt.Sprintf("%d entries", len(entries))
123-
}
124-
}
125-
126-
func ipAllowListEqual(a, b []client.CidrBlockAndDescription) bool {
127-
if len(a) != len(b) {
128-
return false
129-
}
130-
for i := range a {
131-
if a[i].CidrBlock != b[i].CidrBlock || a[i].Description != b[i].Description {
132-
return false
133-
}
134-
}
135-
return true
136-
}

0 commit comments

Comments
 (0)