Skip to content

Commit a0f3275

Browse files
Despiregithub-actions[bot]CI/CD pipeline
authored
Feat/lb alternative names (#1693)
Closes #1662 Adds alternative names support to claudie. This PR only implements the support at the input manifest level and the terraform service. The templates that claudie uses will have to be adjusted individually. The templates can use the following pattern to check if the alternative names extension is used or not ``` // {{- if hasExtension .Data "AlternativeNamesExtension"}} // ... access .AlternativeNamesExtension // {{- end }} ``` --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: CI/CD pipeline <CI/CD-pipeline@users.noreply.github.com>
1 parent 6dbe786 commit a0f3275

22 files changed

Lines changed: 593 additions & 72 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.PHONY: proto manager builder terraformer ansibler kubeEleven test database minio containerimgs crd crd-apply controller-gen kind-load-images
22

33
# Enforce same version of protoc
4-
PROTOC_VERSION = "27.1"
4+
PROTOC_VERSION = "29.3"
55
CURRENT_VERSION = $$(protoc --version | awk '{print $$2}')
66
# Generate all .proto files
77
proto:

internal/clusters/ping4_test.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,7 @@ func TestPingAll(t *testing.T) {
236236
}
237237

238238
for _, got := range got {
239-
found := false
240-
for _, want := range tt.want {
241-
if want == got {
242-
found = true
243-
break
244-
}
245-
}
239+
found := slices.Contains(tt.want, got)
246240
if !found {
247241
t.Fatalf("pingAll() got %v missing in want %v", got, tt.want)
248242
}

internal/manifest/manifest.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,5 +297,12 @@ type DNS struct {
297297
Provider string `validate:"required" yaml:"provider" json:"provider"`
298298
// Custom hostname for your A record. If left empty, the hostname will be a random hash.
299299
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
300-
// Templates for setting up the DNS. (optional)
300+
// Alternative names that will be created in addition to the hostname. Giving the ability
301+
// to have a loadbalancer for multiple hostnames.
302+
//
303+
// - api.example.com
304+
//
305+
// - apiv2.example.com
306+
// +optional
307+
AlternativeNames []string `validate:"dive,required" yaml:"alternativeNames,omitempty" json:"alternativeNames,omitempty"`
301308
}

internal/manifest/validate_load_balancer.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ func (l *LoadBalancer) Validate(m *Manifest) error {
100100
if role == apiServerRole.Name {
101101
// check if this is an ApiServer LB and another ApiServer LB already exists.
102102
if apiServerLBExists[cluster.TargetedK8s] {
103-
return fmt.Errorf("role %q is used across multiple Load-Balancers for k8s-cluster %s. Can have only one ApiServer Load-Balancer per k8s-cluster", role, cluster.TargetedK8s)
103+
return fmt.Errorf("role %q is used across multiple load-balancers for k8s-cluster %s. Can have only one kubeapi-server load-balancer per k8s-cluster", role, cluster.TargetedK8s)
104+
}
105+
if len(cluster.DNS.AlternativeNames) > 0 {
106+
return fmt.Errorf("cannot have alternative names for the kubeapi-server load-balancer cluster %q", cluster.Name)
104107
}
105108

106109
// this is the first LB that uses the ApiServer role.
@@ -123,6 +126,22 @@ func (l *LoadBalancer) Validate(m *Manifest) error {
123126
}
124127
}
125128

129+
// check if alternative names are unique
130+
seen := make(map[string]struct{})
131+
for _, n := range cluster.DNS.AlternativeNames {
132+
if _, ok := seen[n]; ok {
133+
return fmt.Errorf("duplicate alternative names %q specified in cluster %q, must be unique", n, cluster.Name)
134+
}
135+
if cluster.DNS.Hostname != "" && n == cluster.DNS.Hostname {
136+
return fmt.Errorf("alternative name %q has the same value as hostname %q in cluster %q, must be unique", n, cluster.DNS.Hostname, cluster.Name)
137+
}
138+
if zone, ok := hostnamesPerDNS[n]; ok && zone == cluster.DNS.DNSZone {
139+
return fmt.Errorf("alternative name %q used in cluster %q is used across multiple clusters for the same DNS zone %q, must be unique", n, cluster.Name, zone)
140+
}
141+
seen[n] = struct{}{}
142+
hostnamesPerDNS[n] = cluster.DNS.DNSZone
143+
}
144+
126145
// check if the requested hostname is unique per DNS-ZONE
127146
if zone, ok := hostnamesPerDNS[cluster.DNS.Hostname]; ok && zone == cluster.DNS.DNSZone {
128147
return fmt.Errorf("hostname %q used in cluster %q is used across multiple clusters for the same DNS zone %q, must be unique", cluster.DNS.Hostname, cluster.Name, zone)

internal/spectesting/spec_definitions.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -589,16 +589,8 @@ const (
589589
func AddNodes(count int, ci *spec.ClusterInfo, typ NodeFilter) map[string][]string {
590590
affected := make(map[string][]string)
591591

592-
var dyn []*spec.NodePool
593-
var stat []*spec.NodePool
594-
595-
for i := range ci.NodePools {
596-
if ci.NodePools[i].GetStaticNodePool() != nil {
597-
stat = append(stat, ci.NodePools[i])
598-
} else {
599-
dyn = append(dyn, ci.NodePools[i])
600-
}
601-
}
592+
dyn := nodepools.Dynamic(ci.NodePools)
593+
stat := nodepools.Static(ci.NodePools)
602594

603595
if len(dyn) == 0 {
604596
panic("no dynamic nodepools")

internal/templateUtils/templates.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net"
77
"os"
88
"path/filepath"
9+
"reflect"
910
"strings"
1011
"text/template"
1112
)
@@ -53,6 +54,7 @@ func LoadTemplate(tplFile string) (*template.Template, error) {
5354
"replaceAll": strings.ReplaceAll,
5455
"trimPrefix": strings.TrimPrefix,
5556
"extractNetmaskFromCIDR": ExtractNetmaskFromCIDR,
57+
"hasExtension": HasExtension,
5658
}).Parse(tplFile)
5759
if err != nil {
5860
return nil, fmt.Errorf("failed to parse the template file : %w", err)
@@ -70,3 +72,30 @@ func ExtractNetmaskFromCIDR(cidr string) string {
7072
ones, _ := n.Mask.Size()
7173
return fmt.Sprintf("%v", ones)
7274
}
75+
76+
// HasExtensions check whether data is a struct and has the specified 'field', followed
77+
// by a check if the 'field' has a value.
78+
func HasExtension(data any, field string) bool {
79+
// check that the struct 'field' exists within 'data'
80+
rv := reflect.ValueOf(data)
81+
for rv.Kind() == reflect.Pointer || rv.Kind() == reflect.Interface {
82+
rv = rv.Elem()
83+
}
84+
if rv.Kind() != reflect.Struct {
85+
return false
86+
}
87+
88+
val := rv.FieldByName(field)
89+
if !val.IsValid() {
90+
return false
91+
}
92+
93+
// check if the 'field' has a value.
94+
for val.Kind() == reflect.Pointer || val.Kind() == reflect.Interface {
95+
if val.IsZero() {
96+
return false
97+
}
98+
val = val.Elem()
99+
}
100+
return true
101+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package templateUtils
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestHasExtension(t *testing.T) {
10+
type V1 struct {
11+
Field1 string
12+
Field2 string
13+
}
14+
type V2 struct {
15+
Field3 string
16+
Field4 string
17+
}
18+
19+
type Base struct {
20+
Base string
21+
Base1 []string
22+
Base2 struct{ a, b, v string }
23+
}
24+
25+
type ExtendedV1 struct {
26+
Base
27+
V1
28+
}
29+
30+
type ExtendedV1_ struct {
31+
*Base
32+
Extension *V1
33+
}
34+
35+
type ExtendedV2 struct {
36+
Base
37+
V1
38+
V2
39+
}
40+
41+
assert.True(t, HasExtension(Base{}, "Base"))
42+
assert.False(t, HasExtension(Base{}, "V1"))
43+
44+
l1 := &ExtendedV1{}
45+
l2 := &l1
46+
l3 := &l2
47+
l4 := &l3
48+
49+
assert.False(t, HasExtension(ExtendedV1{}, "V2"))
50+
assert.True(t, HasExtension(ExtendedV1{}, "V1"))
51+
assert.True(t, HasExtension(ExtendedV1{}, "Base"))
52+
assert.True(t, HasExtension(ExtendedV1{}, "Base"))
53+
assert.True(t, HasExtension(l4, "Base"))
54+
assert.True(t, HasExtension(l4, "V1"))
55+
assert.True(t, HasExtension(l4, "Field1"))
56+
assert.False(t, HasExtension(l4, "V2"))
57+
58+
l5 := &ExtendedV2{Base: l1.Base, V1: l1.V1}
59+
60+
assert.True(t, HasExtension(ExtendedV2{}, "V2"))
61+
assert.True(t, HasExtension(ExtendedV2{}, "V1"))
62+
assert.True(t, HasExtension(ExtendedV2{}, "Base"))
63+
assert.True(t, HasExtension(ExtendedV2{}, "Base"))
64+
assert.True(t, HasExtension(l5, "Base"))
65+
assert.True(t, HasExtension(l5, "V1"))
66+
assert.True(t, HasExtension(l5, "Field1"))
67+
assert.True(t, HasExtension(l5, "Field3"))
68+
assert.True(t, HasExtension(l5, "Field4"))
69+
assert.True(t, HasExtension(l5, "V2"))
70+
assert.True(t, HasExtension(any(l5), "V2"))
71+
72+
assert.False(t, HasExtension(any(new(int)), "Base"))
73+
assert.False(t, HasExtension(any(nil), "Base"))
74+
assert.False(t, HasExtension((*ExtendedV2)(nil), "V2"))
75+
assert.False(t, HasExtension(nil, "V2"))
76+
77+
assert.False(t, HasExtension(new(ExtendedV1_), "Base"))
78+
assert.False(t, HasExtension(new(ExtendedV1_), "Extension"))
79+
assert.False(t, HasExtension(&ExtendedV1_{Base: new(Base), Extension: nil}, "Extension"))
80+
assert.True(t, HasExtension(&ExtendedV1_{Base: new(Base), Extension: nil}, "Base"))
81+
82+
tmp := new(ExtendedV1_)
83+
assert.False(t, HasExtension(tmp, "Extension"))
84+
assert.False(t, HasExtension(tmp, "Base"))
85+
assert.False(t, HasExtension(tmp.Extension, "Field1"))
86+
tmp.Extension = new(V1)
87+
assert.True(t, HasExtension(tmp, "Extension"))
88+
assert.True(t, HasExtension(tmp.Extension, "Field1"))
89+
tmp.Base = new(Base)
90+
assert.True(t, HasExtension(tmp, "Base"))
91+
assert.True(t, HasExtension(tmp.Base, "Base"))
92+
assert.True(t, HasExtension(tmp.Base, "Base1"))
93+
assert.True(t, HasExtension(tmp.Base, "Base2"))
94+
assert.True(t, HasExtension(tmp.Base.Base2, "a"))
95+
assert.False(t, HasExtension(tmp.Base, "Base3"))
96+
tmp.Extension = nil
97+
assert.False(t, HasExtension(tmp, "Extension"))
98+
}

manifests/claudie/crd/claudie.io_inputmanifests.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ spec:
142142
dns:
143143
description: Specification of the loadbalancer's DNS record.
144144
properties:
145+
alternativeNames:
146+
description: |-
147+
Alternative names that will be created in addition to the hostname. Giving the ability
148+
to have a loadbalancer for multiple hostnames.
149+
- api.example.com
150+
- apiv2.example.com
151+
items:
152+
type: string
153+
type: array
145154
dnsZone:
146155
description:
147156
DNS zone inside of which the records will

manifests/claudie/kustomization.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,18 @@ apiVersion: kustomize.config.k8s.io/v1beta1
5757
kind: Kustomization
5858
images:
5959
- name: ghcr.io/berops/claudie/ansibler
60-
newTag: 053d895-3305
60+
newTag: 7e30277-3306
6161
- name: ghcr.io/berops/claudie/autoscaler-adapter
62-
newTag: 52c9e17-3303
62+
newTag: 7e30277-3306
6363
- name: ghcr.io/berops/claudie/builder
64-
newTag: 52c9e17-3303
64+
newTag: 7e30277-3306
6565
- name: ghcr.io/berops/claudie/claudie-operator
66-
newTag: 053d895-3305
66+
newTag: 7e30277-3306
6767
- name: ghcr.io/berops/claudie/kube-eleven
68-
newTag: 52c9e17-3303
68+
newTag: 7e30277-3306
6969
- name: ghcr.io/berops/claudie/kuber
70-
newTag: 52c9e17-3303
70+
newTag: 7e30277-3306
7171
- name: ghcr.io/berops/claudie/manager
72-
newTag: 52c9e17-3303
72+
newTag: 7e30277-3306
7373
- name: ghcr.io/berops/claudie/terraformer
74-
newTag: 52c9e17-3303
74+
newTag: 7e30277-3306

manifests/testing-framework/kustomization.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@ secretGenerator:
9090

9191
images:
9292
- name: ghcr.io/berops/claudie/testing-framework
93-
newTag: 52c9e17-3303
93+
newTag: 7e30277-3306

0 commit comments

Comments
 (0)