Skip to content

Commit dcdcb49

Browse files
committed
Add cluster tagging and domain filtering features in README and config
- Introduced automatic cluster tagging for services from remote clusters, allowing customization of badge colors via the `cluster-tagstyle` label. - Enhanced per-cluster domain filtering capabilities, enabling independent domain specifications for each cluster. - Updated documentation to reflect these new features and their usage.
1 parent 3511b6c commit dcdcb49

File tree

3 files changed

+324
-1
lines changed

3 files changed

+324
-1
lines changed

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ spec:
703703
cluster: production
704704
region: us-west-2
705705
team: platform
706+
cluster-tagstyle: "is-danger" # Optional: Customize badge color (red/yellow/blue/green/gray)
706707
707708
# Resource filtering (optional) - same as main cluster
708709
ingressSelector:
@@ -717,10 +718,23 @@ spec:
717718
matchLabels:
718719
type: ingress
719720
721+
# Per-cluster domain filtering (optional) - overrides global domainFilters for this cluster
720722
domainFilters:
721-
- "example.com"
723+
- "prod.example.com" # Only production domains from this cluster
724+
- "api.example.com" # API endpoints
722725
```
723726

727+
**Automatic Cluster Tagging:**
728+
Services from remote clusters automatically get badge tags with the cluster name. Customize colors using the `cluster-tagstyle` label:
729+
- `is-danger` (red) - production
730+
- `is-warning` (yellow) - staging
731+
- `is-info` (blue, default) - development
732+
- `is-success` (green) - QA
733+
- `is-light` (gray) - deprecated
734+
735+
**Per-Cluster Domain Filtering:**
736+
Each cluster can have independent `domainFilters` to control which services are discovered. If not specified, inherits from the main `spec.domainFilters`.
737+
724738
### Status Monitoring
725739

726740
Check multi-cluster connection status:

pkg/homer/cluster_tag_test.go

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package homer
2+
3+
import (
4+
"testing"
5+
6+
networkingv1 "k8s.io/api/networking/v1"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
9+
)
10+
11+
// TestIngressClusterAutoTag verifies that Ingresses from remote clusters get auto-tagged
12+
func TestIngressClusterAutoTag(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
clusterAnnot string
16+
tagStyleLabel string
17+
expectTag bool
18+
expectedTagName string
19+
expectedTagStyle string
20+
}{
21+
{
22+
name: "Remote cluster ottawa - default blue",
23+
clusterAnnot: "ottawa",
24+
expectTag: true,
25+
expectedTagName: "ottawa",
26+
expectedTagStyle: "is-info", // Default blue
27+
},
28+
{
29+
name: "Remote cluster robbinsdale - default blue",
30+
clusterAnnot: "robbinsdale",
31+
expectTag: true,
32+
expectedTagName: "robbinsdale",
33+
expectedTagStyle: "is-info", // Default blue
34+
},
35+
{
36+
name: "Remote cluster with custom red color",
37+
clusterAnnot: "ottawa",
38+
tagStyleLabel: "is-danger",
39+
expectTag: true,
40+
expectedTagName: "ottawa",
41+
expectedTagStyle: "is-danger", // Custom red
42+
},
43+
{
44+
name: "Remote cluster with custom yellow color",
45+
clusterAnnot: "staging",
46+
tagStyleLabel: "is-warning",
47+
expectTag: true,
48+
expectedTagName: "staging",
49+
expectedTagStyle: "is-warning", // Custom yellow
50+
},
51+
{
52+
name: "Local cluster",
53+
clusterAnnot: "local",
54+
expectTag: false,
55+
},
56+
{
57+
name: "No cluster annotation",
58+
expectTag: false,
59+
},
60+
}
61+
62+
for _, tt := range tests {
63+
t.Run(tt.name, func(t *testing.T) {
64+
ingress := &networkingv1.Ingress{
65+
ObjectMeta: metav1.ObjectMeta{
66+
Name: "test-ingress",
67+
Namespace: "default",
68+
Annotations: map[string]string{},
69+
Labels: map[string]string{},
70+
},
71+
Spec: networkingv1.IngressSpec{
72+
Rules: []networkingv1.IngressRule{
73+
{Host: "test.example.com"},
74+
},
75+
},
76+
}
77+
78+
if tt.clusterAnnot != "" {
79+
ingress.ObjectMeta.Annotations["homer.rajsingh.info/cluster"] = tt.clusterAnnot
80+
}
81+
82+
if tt.tagStyleLabel != "" {
83+
ingress.ObjectMeta.Labels["cluster-tagstyle"] = tt.tagStyleLabel
84+
}
85+
86+
config := &HomerConfig{
87+
Title: "Test Dashboard",
88+
Header: true,
89+
}
90+
91+
UpdateHomerConfigIngress(config, *ingress, nil)
92+
93+
if len(config.Services) == 0 {
94+
t.Fatal("Expected at least one service")
95+
}
96+
97+
service := config.Services[0]
98+
if len(service.Items) == 0 {
99+
t.Fatal("Expected at least one item")
100+
}
101+
102+
item := service.Items[0]
103+
104+
if tt.expectTag {
105+
tag, hasTag := item.Parameters["tag"]
106+
if !hasTag {
107+
t.Errorf("Expected tag to be set for cluster %s", tt.clusterAnnot)
108+
}
109+
if tag != tt.expectedTagName {
110+
t.Errorf("Expected tag %q, got %q", tt.expectedTagName, tag)
111+
}
112+
113+
tagstyle, hasTagStyle := item.Parameters["tagstyle"]
114+
if !hasTagStyle {
115+
t.Error("Expected tagstyle to be set")
116+
}
117+
if tagstyle != tt.expectedTagStyle {
118+
t.Errorf("Expected tagstyle %q, got %q", tt.expectedTagStyle, tagstyle)
119+
}
120+
} else {
121+
if tag, hasTag := item.Parameters["tag"]; hasTag {
122+
t.Errorf("Expected no tag for %s, but got %q", tt.name, tag)
123+
}
124+
}
125+
})
126+
}
127+
}
128+
129+
// TestHTTPRouteClusterAutoTag verifies that HTTPRoutes from remote clusters get auto-tagged
130+
func TestHTTPRouteClusterAutoTag(t *testing.T) {
131+
tests := []struct {
132+
name string
133+
clusterAnnot string
134+
tagStyleLabel string
135+
expectTag bool
136+
expectedTagName string
137+
expectedTagStyle string
138+
}{
139+
{
140+
name: "Remote cluster ottawa - default blue",
141+
clusterAnnot: "ottawa",
142+
expectTag: true,
143+
expectedTagName: "ottawa",
144+
expectedTagStyle: "is-info", // Default blue
145+
},
146+
{
147+
name: "Remote cluster robbinsdale - default blue",
148+
clusterAnnot: "robbinsdale",
149+
expectTag: true,
150+
expectedTagName: "robbinsdale",
151+
expectedTagStyle: "is-info", // Default blue
152+
},
153+
{
154+
name: "Remote cluster with custom red color",
155+
clusterAnnot: "production",
156+
tagStyleLabel: "is-danger",
157+
expectTag: true,
158+
expectedTagName: "production",
159+
expectedTagStyle: "is-danger", // Custom red
160+
},
161+
{
162+
name: "Local cluster",
163+
clusterAnnot: "local",
164+
expectTag: false,
165+
},
166+
{
167+
name: "No cluster annotation",
168+
expectTag: false,
169+
},
170+
}
171+
172+
for _, tt := range tests {
173+
t.Run(tt.name, func(t *testing.T) {
174+
hostname := gatewayv1.Hostname("test.example.com")
175+
httproute := &gatewayv1.HTTPRoute{
176+
ObjectMeta: metav1.ObjectMeta{
177+
Name: "test-httproute",
178+
Namespace: "default",
179+
Annotations: map[string]string{},
180+
Labels: map[string]string{},
181+
},
182+
Spec: gatewayv1.HTTPRouteSpec{
183+
Hostnames: []gatewayv1.Hostname{hostname},
184+
},
185+
}
186+
187+
if tt.clusterAnnot != "" {
188+
httproute.ObjectMeta.Annotations["homer.rajsingh.info/cluster"] = tt.clusterAnnot
189+
}
190+
191+
if tt.tagStyleLabel != "" {
192+
httproute.ObjectMeta.Labels["cluster-tagstyle"] = tt.tagStyleLabel
193+
}
194+
195+
config := &HomerConfig{
196+
Title: "Test Dashboard",
197+
Header: true,
198+
}
199+
200+
UpdateHomerConfigHTTPRoute(config, httproute, nil)
201+
202+
if len(config.Services) == 0 {
203+
t.Fatal("Expected at least one service")
204+
}
205+
206+
service := config.Services[0]
207+
if len(service.Items) == 0 {
208+
t.Fatal("Expected at least one item")
209+
}
210+
211+
item := service.Items[0]
212+
213+
if tt.expectTag {
214+
tag, hasTag := item.Parameters["tag"]
215+
if !hasTag {
216+
t.Errorf("Expected tag to be set for cluster %s", tt.clusterAnnot)
217+
}
218+
if tag != tt.expectedTagName {
219+
t.Errorf("Expected tag %q, got %q", tt.expectedTagName, tag)
220+
}
221+
222+
tagstyle, hasTagStyle := item.Parameters["tagstyle"]
223+
if !hasTagStyle {
224+
t.Error("Expected tagstyle to be set")
225+
}
226+
if tagstyle != tt.expectedTagStyle {
227+
t.Errorf("Expected tagstyle %q, got %q", tt.expectedTagStyle, tagstyle)
228+
}
229+
} else {
230+
if tag, hasTag := item.Parameters["tag"]; hasTag {
231+
t.Errorf("Expected no tag for %s, but got %q", tt.name, tag)
232+
}
233+
}
234+
})
235+
}
236+
}
237+
238+
// TestClusterTagNotOverriddenByAnnotations verifies that manual annotations take precedence
239+
func TestClusterTagNotOverriddenByAnnotations(t *testing.T) {
240+
ingress := &networkingv1.Ingress{
241+
ObjectMeta: metav1.ObjectMeta{
242+
Name: "test-ingress",
243+
Namespace: "default",
244+
Annotations: map[string]string{
245+
"homer.rajsingh.info/cluster": "ottawa",
246+
"item.homer.rajsingh.info/tag": "production",
247+
"item.homer.rajsingh.info/tagstyle": "is-danger",
248+
},
249+
},
250+
Spec: networkingv1.IngressSpec{
251+
Rules: []networkingv1.IngressRule{
252+
{Host: "test.example.com"},
253+
},
254+
},
255+
}
256+
257+
config := &HomerConfig{
258+
Title: "Test Dashboard",
259+
Header: true,
260+
}
261+
262+
UpdateHomerConfigIngress(config, *ingress, nil)
263+
264+
if len(config.Services) == 0 {
265+
t.Fatal("Expected at least one service")
266+
}
267+
268+
service := config.Services[0]
269+
if len(service.Items) == 0 {
270+
t.Fatal("Expected at least one item")
271+
}
272+
273+
item := service.Items[0]
274+
275+
// Manual annotation should take precedence over auto-tag
276+
tag := item.Parameters["tag"]
277+
if tag != "production" {
278+
t.Errorf("Expected manual tag 'production', got %q", tag)
279+
}
280+
281+
tagstyle := item.Parameters["tagstyle"]
282+
if tagstyle != "is-danger" {
283+
t.Errorf("Expected manual tagstyle 'is-danger', got %q", tagstyle)
284+
}
285+
}

pkg/homer/config.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,18 @@ func createIngressItem(ingress networkingv1.Ingress, host string, validRuleCount
11771177
item.Namespace = ingress.ObjectMeta.Namespace
11781178
item.LastUpdate = ingress.ObjectMeta.CreationTimestamp.Time.Format("2006-01-02T15:04:05Z")
11791179

1180+
// Auto-tag with cluster name if present
1181+
if clusterName, ok := ingress.ObjectMeta.Annotations["homer.rajsingh.info/cluster"]; ok && clusterName != "" && clusterName != "local" {
1182+
setItemParameter(&item, "tag", clusterName)
1183+
1184+
// Check if cluster-tagstyle label is set, otherwise use default blue
1185+
tagStyle := "is-info" // Default blue
1186+
if customStyle, hasStyle := ingress.ObjectMeta.Labels["cluster-tagstyle"]; hasStyle && customStyle != "" {
1187+
tagStyle = customStyle
1188+
}
1189+
setItemParameter(&item, "tagstyle", tagStyle)
1190+
}
1191+
11801192
return item
11811193
}
11821194

@@ -1300,6 +1312,18 @@ func createHTTPRouteItem(httproute *gatewayv1.HTTPRoute, hostname, protocol stri
13001312
setItemParameter(&item, "subtitle", "")
13011313
}
13021314

1315+
// Auto-tag with cluster name if present
1316+
if clusterName, ok := httproute.ObjectMeta.Annotations["homer.rajsingh.info/cluster"]; ok && clusterName != "" && clusterName != "local" {
1317+
setItemParameter(&item, "tag", clusterName)
1318+
1319+
// Check if cluster-tagstyle label is set, otherwise use default blue
1320+
tagStyle := "is-info" // Default blue
1321+
if customStyle, hasStyle := httproute.ObjectMeta.Labels["cluster-tagstyle"]; hasStyle && customStyle != "" {
1322+
tagStyle = customStyle
1323+
}
1324+
setItemParameter(&item, "tagstyle", tagStyle)
1325+
}
1326+
13031327
return item
13041328
}
13051329

0 commit comments

Comments
 (0)