Skip to content

Commit 60d2cc3

Browse files
imkiraajithcnambiarWojtek Koronski
committed
feat: multinamespace name config
Support more flexible namespace naming format in the multi name space mode via configuration. Co-authored-by: Ajith Chandran <[email protected]> Co-authored-by: Wojtek Koronski <[email protected]>
1 parent 72d9ee4 commit 60d2cc3

File tree

11 files changed

+331
-14
lines changed

11 files changed

+331
-14
lines changed

chart/values.schema.json

+26
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,10 @@
13601360
"type": "boolean",
13611361
"description": "Enabled specifies if multi namespace mode should get enabled"
13621362
},
1363+
"namespaceNameFormat": {
1364+
"$ref": "#/$defs/ExperimentalMultiNamespaceNameFormat",
1365+
"description": "NamespaceNameFormat allows you to customize the name of the physical namespaces."
1366+
},
13631367
"namespaceLabels": {
13641368
"additionalProperties": {
13651369
"type": "string"
@@ -1371,6 +1375,28 @@
13711375
"additionalProperties": false,
13721376
"type": "object"
13731377
},
1378+
"ExperimentalMultiNamespaceNameFormat": {
1379+
"properties": {
1380+
"prefix": {
1381+
"type": "string",
1382+
"description": "Prefix is the prefix added to the physical namespaces.\nIf empty, the default is to use \"vcluster\""
1383+
},
1384+
"rawBase": {
1385+
"type": "boolean",
1386+
"description": "If RawBase is true, use the virtual namespace as is, otherwise hash it."
1387+
},
1388+
"rawSuffix": {
1389+
"type": "boolean",
1390+
"description": "If RawSuffix is true, use the cluster name as is, otherwise hash it."
1391+
},
1392+
"avoidRedundantFormatting": {
1393+
"type": "boolean",
1394+
"description": "If AvoidRedundantFormatting is true, we check if base (the name between prefix and suffix)\nalready contains the prefix and suffix. In that case, we just return base, instead of\nformatting again. Otherwise, we always add prefix and suffix."
1395+
}
1396+
},
1397+
"additionalProperties": false,
1398+
"type": "object"
1399+
},
13741400
"ExperimentalSyncSettings": {
13751401
"properties": {
13761402
"disableSync": {

chart/values.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,8 @@ experimental:
821821
multiNamespaceMode:
822822
# Enabled specifies if multi namespace mode should get enabled
823823
enabled: false
824+
# NamespaceNameFormat allows you to customize the name of the physical namespaces.
825+
namespaceNameFormat: {}
824826

825827
# SyncSettings are advanced settings for the syncer controller.
826828
syncSettings:

config/config.go

+20
Original file line numberDiff line numberDiff line change
@@ -1679,10 +1679,30 @@ type ExperimentalMultiNamespaceMode struct {
16791679
// Enabled specifies if multi namespace mode should get enabled
16801680
Enabled bool `json:"enabled,omitempty"`
16811681

1682+
// NamespaceNameFormat allows you to customize the name of the physical namespaces.
1683+
NamespaceNameFormat ExperimentalMultiNamespaceNameFormat `json:"namespaceNameFormat,omitempty"`
1684+
16821685
// NamespaceLabels are extra labels that will be added by vCluster to each created namespace.
16831686
NamespaceLabels map[string]string `json:"namespaceLabels,omitempty"`
16841687
}
16851688

1689+
type ExperimentalMultiNamespaceNameFormat struct {
1690+
// Prefix is the prefix added to the physical namespaces.
1691+
// If empty, the default is to use "vcluster"
1692+
Prefix string `json:"prefix,omitempty"`
1693+
1694+
// If RawBase is true, use the virtual namespace as is, otherwise hash it.
1695+
RawBase bool `json:"rawBase,omitempty"`
1696+
1697+
// If RawSuffix is true, use the cluster name as is, otherwise hash it.
1698+
RawSuffix bool `json:"rawSuffix,omitempty"`
1699+
1700+
// If AvoidRedundantFormatting is true, we check if base (the name between prefix and suffix)
1701+
// already contains the prefix and suffix. In that case, we just return base, instead of
1702+
// formatting again. Otherwise, we always add prefix and suffix.
1703+
AvoidRedundantFormatting bool `json:"avoidRedundantFormatting,omitempty"`
1704+
}
1705+
16861706
type ExperimentalIsolatedControlPlane struct {
16871707
// Enabled specifies if the isolated control plane feature should be enabled.
16881708
Enabled bool `json:"enabled,omitempty" product:"pro"`

config/config_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,23 @@ func TestConfig_IsProFeatureEnabled(t *testing.T) {
334334
},
335335
expected: true,
336336
},
337+
{
338+
name: "Namespace reformatting on multinamespace mode does not use Pro features",
339+
config: &Config{
340+
Experimental: Experimental{
341+
MultiNamespaceMode: ExperimentalMultiNamespaceMode{
342+
Enabled: true,
343+
NamespaceNameFormat: ExperimentalMultiNamespaceNameFormat{
344+
Prefix: "foo",
345+
RawBase: true,
346+
RawSuffix: true,
347+
AvoidRedundantFormatting: true,
348+
},
349+
},
350+
},
351+
},
352+
expected: false,
353+
},
337354
}
338355

339356
for _, tt := range tests {

config/values.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ plugins: {}
491491
experimental:
492492
multiNamespaceMode:
493493
enabled: false
494+
namespaceNameFormat: {}
494495

495496
syncSettings:
496497
disableSync: false

pkg/config/validation.go

+5
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ func ValidateConfigAndSetDefaults(config *VirtualClusterConfig) error {
100100
}
101101
}
102102

103+
// set multi namespace mode name format
104+
if config.Experimental.MultiNamespaceMode.NamespaceNameFormat.Prefix == "" {
105+
config.Experimental.MultiNamespaceMode.NamespaceNameFormat.Prefix = "vcluster"
106+
}
107+
103108
// check resolve dns
104109
err = validateMappings(config.Networking.ResolveDNS)
105110
if err != nil {

pkg/config/validation_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"fmt"
45
"testing"
56

67
"github.com/loft-sh/vcluster/config"
@@ -125,3 +126,58 @@ func mutHook(clientCfg config.ValidatingWebhookClientConfig) config.MutatingWebh
125126
}
126127
return hook
127128
}
129+
130+
func TestValidateConfigAndSetDefaults(t *testing.T) {
131+
testCases := []struct {
132+
name string
133+
wantErr string
134+
config VirtualClusterConfig
135+
checkFunc func(config *VirtualClusterConfig) error
136+
}{
137+
{
138+
name: "multi-namespace namespace name formatting default prefix",
139+
wantErr: "",
140+
config: VirtualClusterConfig{},
141+
checkFunc: func(config *VirtualClusterConfig) error {
142+
if prefix := config.Experimental.MultiNamespaceMode.NamespaceNameFormat.Prefix; prefix != "vcluster" {
143+
return fmt.Errorf("unexpected prefix %q", prefix)
144+
}
145+
return nil
146+
},
147+
},
148+
{
149+
name: "multi-namespace namespace name formatting custom prefix",
150+
wantErr: "",
151+
config: VirtualClusterConfig{
152+
Config: config.Config{
153+
Experimental: config.Experimental{
154+
MultiNamespaceMode: config.ExperimentalMultiNamespaceMode{
155+
NamespaceNameFormat: config.ExperimentalMultiNamespaceNameFormat{
156+
Prefix: "foo",
157+
},
158+
},
159+
},
160+
},
161+
},
162+
checkFunc: func(config *VirtualClusterConfig) error {
163+
if prefix := config.Experimental.MultiNamespaceMode.NamespaceNameFormat.Prefix; prefix != "foo" {
164+
return fmt.Errorf("unexpected prefix %q", prefix)
165+
}
166+
return nil
167+
},
168+
},
169+
}
170+
for _, tt := range testCases {
171+
t.Run(tt.name, func(t *testing.T) {
172+
outputConfig := tt.config
173+
err := ValidateConfigAndSetDefaults(&outputConfig)
174+
if err != nil && (tt.wantErr == "" || tt.wantErr != err.Error()) {
175+
t.Errorf("wanted err to be %s but got %s", tt.wantErr, err.Error())
176+
} else if err == nil && tt.wantErr != "" {
177+
t.Errorf("wanted err to be %s but got nil", tt.wantErr)
178+
} else if err := tt.checkFunc(&outputConfig); err != nil {
179+
t.Errorf("wanted check err to be nil but got %s", err.Error())
180+
}
181+
})
182+
}
183+
}

pkg/setup/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func InitAndValidateConfig(ctx context.Context, vConfig *config.VirtualClusterCo
4747

4848
// get workload target namespace
4949
if vConfig.Experimental.MultiNamespaceMode.Enabled {
50-
translate.Default = translate.NewMultiNamespaceTranslator(vConfig.WorkloadNamespace)
50+
translate.Default = translate.NewMultiNamespaceTranslator(vConfig.WorkloadNamespace, vConfig.Experimental.MultiNamespaceMode.NamespaceNameFormat)
5151
} else {
5252
// ensure target namespace
5353
vConfig.WorkloadTargetNamespace = vConfig.Experimental.SyncSettings.TargetNamespace

pkg/util/translate/multi_namespace.go

+24-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"strings"
88

9+
"github.com/loft-sh/vcluster/config"
910
"github.com/loft-sh/vcluster/pkg/scheme"
1011
"github.com/loft-sh/vcluster/pkg/syncer/synccontext"
1112
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -14,14 +15,16 @@ import (
1415

1516
var _ Translator = &multiNamespace{}
1617

17-
func NewMultiNamespaceTranslator(currentNamespace string) Translator {
18+
func NewMultiNamespaceTranslator(currentNamespace string, nameFormat config.ExperimentalMultiNamespaceNameFormat) Translator {
1819
return &multiNamespace{
1920
currentNamespace: currentNamespace,
21+
nameFormat: nameFormat,
2022
}
2123
}
2224

2325
type multiNamespace struct {
2426
currentNamespace string
27+
nameFormat config.ExperimentalMultiNamespaceNameFormat
2528
}
2629

2730
func (s *multiNamespace) SingleNamespaceTarget() bool {
@@ -68,25 +71,34 @@ func (s *multiNamespace) IsManaged(_ *synccontext.SyncContext, pObj client.Objec
6871
}
6972

7073
func (s *multiNamespace) IsTargetedNamespace(ns string) bool {
71-
return strings.HasPrefix(ns, s.getNamespacePrefix()) && strings.HasSuffix(ns, getNamespaceSuffix(s.currentNamespace, VClusterName))
74+
return strings.HasPrefix(ns, s.getNamespacePrefix()) && strings.HasSuffix(ns, s.getNamespaceSuffix())
7275
}
7376

7477
func (s *multiNamespace) getNamespacePrefix() string {
75-
return "vcluster"
78+
return s.nameFormat.Prefix
7679
}
7780

7881
func (s *multiNamespace) HostNamespace(vNamespace string) string {
79-
return hostNamespace(s.currentNamespace, vNamespace, s.getNamespacePrefix(), VClusterName)
80-
}
81-
82-
func hostNamespace(currentNamespace, vNamespace, prefix, suffix string) string {
83-
sha := sha256.Sum256([]byte(vNamespace))
84-
return fmt.Sprintf("%s-%s-%s", prefix, hex.EncodeToString(sha[0:])[0:8], getNamespaceSuffix(currentNamespace, suffix))
82+
base := vNamespace
83+
if !s.nameFormat.RawBase {
84+
sha := sha256.Sum256([]byte(base))
85+
base = hex.EncodeToString(sha[0:])[0:8]
86+
}
87+
if s.nameFormat.AvoidRedundantFormatting && s.IsTargetedNamespace(base) {
88+
return base
89+
}
90+
prefix := s.getNamespacePrefix()
91+
suffix := s.getNamespaceSuffix()
92+
return fmt.Sprintf("%s-%s-%s", prefix, base, suffix)
8593
}
8694

87-
func getNamespaceSuffix(currentNamespace, suffix string) string {
88-
sha := sha256.Sum256([]byte(currentNamespace + "x" + suffix))
89-
return hex.EncodeToString(sha[0:])[0:8]
95+
func (s *multiNamespace) getNamespaceSuffix() string {
96+
suffix := VClusterName
97+
if !s.nameFormat.RawSuffix {
98+
sha := sha256.Sum256([]byte(s.currentNamespace + "x" + suffix))
99+
suffix = hex.EncodeToString(sha[0:])[0:8]
100+
}
101+
return suffix
90102
}
91103

92104
func (s *multiNamespace) MarkerLabelCluster() string {

0 commit comments

Comments
 (0)