Skip to content

Commit 60b2704

Browse files
committed
feat(kubernetes): add provider.kubernetes.delimiter config property
the delimiter configuration property for kubernetes allows you to change the default "_" delimiter. By specifying "/" or "." as a delimiter you ensure that you won't collide with objects names that contains the default delimiter "_". Fixes #207
1 parent 9947881 commit 60b2704

13 files changed

Lines changed: 126 additions & 33 deletions

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"ghcr.io/devcontainers/features/node:1": {
66
"version": "lts"
77
},
8-
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
8+
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
99
"ghcr.io/devcontainers/features/git:1": {},
1010
"ghcr.io/devcontainers/features/go:1": {}
1111
},

app/providers/kubernetes.go

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ import (
2222
"k8s.io/client-go/tools/cache"
2323
)
2424

25-
// Delimiter is used to split name into kind,namespace,name,replicacount
26-
const Delimiter = "_"
27-
2825
type Config struct {
2926
OriginalName string
3027
Kind string // deployment or statefulset
@@ -38,11 +35,10 @@ type Workload interface {
3835
UpdateScale(ctx context.Context, workloadName string, scale *autoscalingv1.Scale, opts metav1.UpdateOptions) (*autoscalingv1.Scale, error)
3936
}
4037

41-
func convertName(name string) (*Config, error) {
42-
// name format kind_namespace_name_replicas
43-
s := strings.Split(name, Delimiter)
38+
func (provider *KubernetesProvider) convertName(name string) (*Config, error) {
39+
s := strings.Split(name, provider.delimiter)
4440
if len(s) < 4 {
45-
return nil, errors.New("invalid name should be: kind" + Delimiter + "namespace" + Delimiter + "name" + Delimiter + "replicas")
41+
return nil, errors.New("invalid name should be: kind" + provider.delimiter + "namespace" + provider.delimiter + "name" + provider.delimiter + "replicas")
4642
}
4743
replicas, err := strconv.Atoi(s[3])
4844
if err != nil {
@@ -58,8 +54,18 @@ func convertName(name string) (*Config, error) {
5854
}, nil
5955
}
6056

57+
func (provider *KubernetesProvider) convertStatefulset(ss *appsv1.StatefulSet, replicas int32) string {
58+
return fmt.Sprintf("statefulset%s%s%s%s%s%d", provider.delimiter, ss.Namespace, provider.delimiter, ss.Name, provider.delimiter, replicas)
59+
}
60+
61+
func (provider *KubernetesProvider) convertDeployment(d *appsv1.Deployment, replicas int32) string {
62+
return fmt.Sprintf("statefulset%s%s%s%s%s%d", provider.delimiter, d.Namespace, provider.delimiter, d.Name, provider.delimiter, replicas)
63+
64+
}
65+
6166
type KubernetesProvider struct {
62-
Client kubernetes.Interface
67+
Client kubernetes.Interface
68+
delimiter string
6369
}
6470

6571
func NewKubernetesProvider(providerConfig providerConfig.Kubernetes) (*KubernetesProvider, error) {
@@ -79,13 +85,14 @@ func NewKubernetesProvider(providerConfig providerConfig.Kubernetes) (*Kubernete
7985
}
8086

8187
return &KubernetesProvider{
82-
Client: client,
88+
Client: client,
89+
delimiter: providerConfig.Delimiter,
8390
}, nil
8491

8592
}
8693

8794
func (provider *KubernetesProvider) Start(ctx context.Context, name string) (instance.State, error) {
88-
config, err := convertName(name)
95+
config, err := provider.convertName(name)
8996
if err != nil {
9097
return instance.UnrecoverableInstanceState(name, err.Error(), int(config.Replicas))
9198
}
@@ -94,7 +101,7 @@ func (provider *KubernetesProvider) Start(ctx context.Context, name string) (ins
94101
}
95102

96103
func (provider *KubernetesProvider) Stop(ctx context.Context, name string) (instance.State, error) {
97-
config, err := convertName(name)
104+
config, err := provider.convertName(name)
98105
if err != nil {
99106
return instance.UnrecoverableInstanceState(name, err.Error(), int(config.Replicas))
100107
}
@@ -121,12 +128,11 @@ func (provider *KubernetesProvider) GetGroups(ctx context.Context) (map[string][
121128

122129
group := groups[groupName]
123130
// TOOD: Use annotation for scale
124-
name := fmt.Sprintf("%s_%s_%s_%d", "deployment", deployment.Namespace, deployment.Name, 1)
131+
name := provider.convertDeployment(&deployment, 1)
125132
group = append(group, name)
126133
groups[groupName] = group
127134
}
128135

129-
130136
statefulSets, err := provider.Client.AppsV1().StatefulSets(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{
131137
LabelSelector: enableLabel,
132138
})
@@ -143,7 +149,7 @@ func (provider *KubernetesProvider) GetGroups(ctx context.Context) (map[string][
143149

144150
group := groups[groupName]
145151
// TOOD: Use annotation for scale
146-
name := fmt.Sprintf("%s_%s_%s_%d", "statefulset", statefulSet.Namespace, statefulSet.Name, 1)
152+
name := provider.convertStatefulset(&statefulSet, 1)
147153
group = append(group, name)
148154
groups[groupName] = group
149155
}
@@ -179,7 +185,7 @@ func (provider *KubernetesProvider) scale(ctx context.Context, config *Config, r
179185
}
180186

181187
func (provider *KubernetesProvider) GetState(ctx context.Context, name string) (instance.State, error) {
182-
config, err := convertName(name)
188+
config, err := provider.convertName(name)
183189
if err != nil {
184190
return instance.UnrecoverableInstanceState(name, err.Error(), int(config.Replicas))
185191
}
@@ -243,12 +249,12 @@ func (provider *KubernetesProvider) watchDeployents(instance chan<- string) cach
243249
}
244250

245251
if *newDeployment.Spec.Replicas == 0 {
246-
instance <- fmt.Sprintf("deployment_%s_%s_%d", newDeployment.Namespace, newDeployment.Name, *oldDeployment.Spec.Replicas)
252+
instance <- provider.convertDeployment(newDeployment, *oldDeployment.Spec.Replicas)
247253
}
248254
},
249255
DeleteFunc: func(obj interface{}) {
250256
deletedDeployment := obj.(*appsv1.Deployment)
251-
instance <- fmt.Sprintf("deployment_%s_%s_%d", deletedDeployment.Namespace, deletedDeployment.Name, *deletedDeployment.Spec.Replicas)
257+
instance <- provider.convertDeployment(deletedDeployment, *deletedDeployment.Spec.Replicas)
252258
},
253259
}
254260
factory := informers.NewSharedInformerFactoryWithOptions(provider.Client, 2*time.Second, informers.WithNamespace(core_v1.NamespaceAll))
@@ -269,12 +275,12 @@ func (provider *KubernetesProvider) watchStatefulSets(instance chan<- string) ca
269275
}
270276

271277
if *newStatefulSet.Spec.Replicas == 0 {
272-
instance <- fmt.Sprintf("statefulset_%s_%s_%d", newStatefulSet.Namespace, newStatefulSet.Name, *oldStatefulSet.Spec.Replicas)
278+
instance <- provider.convertStatefulset(newStatefulSet, *oldStatefulSet.Spec.Replicas)
273279
}
274280
},
275281
DeleteFunc: func(obj interface{}) {
276282
deletedStatefulSet := obj.(*appsv1.StatefulSet)
277-
instance <- fmt.Sprintf("statefulset__%s_%s_%d", deletedStatefulSet.Namespace, deletedStatefulSet.Name, *deletedStatefulSet.Spec.Replicas)
283+
instance <- provider.convertStatefulset(deletedStatefulSet, *deletedStatefulSet.Spec.Replicas)
278284
},
279285
}
280286
factory := informers.NewSharedInformerFactoryWithOptions(provider.Client, 2*time.Second, informers.WithNamespace(core_v1.NamespaceAll))

app/providers/kubernetes_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ func TestKubernetesProvider_Start(t *testing.T) {
9090
deploymentAPI := mocks.DeploymentMock{}
9191
statefulsetAPI := mocks.StatefulSetsMock{}
9292
provider := KubernetesProvider{
93-
Client: mocks.NewKubernetesAPIClientMock(&deploymentAPI, &statefulsetAPI),
93+
Client: mocks.NewKubernetesAPIClientMock(&deploymentAPI, &statefulsetAPI),
94+
delimiter: "_",
9495
}
9596

9697
deploymentAPI.On("GetScale", mock.Anything, tt.data.name, metav1.GetOptions{}).Return(tt.data.get, nil)
@@ -188,7 +189,8 @@ func TestKubernetesProvider_Stop(t *testing.T) {
188189
deploymentAPI := mocks.DeploymentMock{}
189190
statefulsetAPI := mocks.StatefulSetsMock{}
190191
provider := KubernetesProvider{
191-
Client: mocks.NewKubernetesAPIClientMock(&deploymentAPI, &statefulsetAPI),
192+
Client: mocks.NewKubernetesAPIClientMock(&deploymentAPI, &statefulsetAPI),
193+
delimiter: "_",
192194
}
193195

194196
deploymentAPI.On("GetScale", mock.Anything, tt.data.name, metav1.GetOptions{}).Return(tt.data.get, nil)
@@ -316,7 +318,8 @@ func TestKubernetesProvider_GetState(t *testing.T) {
316318
deploymentAPI := mocks.DeploymentMock{}
317319
statefulsetAPI := mocks.StatefulSetsMock{}
318320
provider := KubernetesProvider{
319-
Client: mocks.NewKubernetesAPIClientMock(&deploymentAPI, &statefulsetAPI),
321+
Client: mocks.NewKubernetesAPIClientMock(&deploymentAPI, &statefulsetAPI),
322+
delimiter: "_",
320323
}
321324

322325
deploymentAPI.On("Get", mock.Anything, tt.data.name, metav1.GetOptions{}).Return(tt.data.getDeployment, nil)

cmd/root.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ It provides an integrations with multiple reverse proxies and different loading
4646
// Provider flags
4747
startCmd.Flags().StringVar(&conf.Provider.Name, "provider.name", "docker", fmt.Sprintf("Provider to use to manage containers %v", config.GetProviders()))
4848
viper.BindPFlag("provider.name", startCmd.Flags().Lookup("provider.name"))
49-
startCmd.Flags().Float32Var(&conf.Provider.Kubernetes.QPS, "provider.kubernetes.qps", 5, fmt.Sprintf("QPS limit for K8S API access client-side throttling"))
49+
startCmd.Flags().Float32Var(&conf.Provider.Kubernetes.QPS, "provider.kubernetes.qps", 5, "QPS limit for K8S API access client-side throttling")
5050
viper.BindPFlag("provider.kubernetes.qps", startCmd.Flags().Lookup("provider.kubernetes.qps"))
51-
startCmd.Flags().IntVar(&conf.Provider.Kubernetes.Burst, "provider.kubernetes.burst", 10, fmt.Sprintf("Maximum burst for K8S API acees client-side throttling"))
51+
startCmd.Flags().IntVar(&conf.Provider.Kubernetes.Burst, "provider.kubernetes.burst", 10, "Maximum burst for K8S API acees client-side throttling")
5252
viper.BindPFlag("provider.kubernetes.burst", startCmd.Flags().Lookup("provider.kubernetes.burst"))
53+
startCmd.Flags().StringVar(&conf.Provider.Kubernetes.Delimiter, "provider.kubernetes.delimiter", "_", "Delimiter used for namespace/resource type/name resolution. Defaults to \"_\" for backward compatibility. But you should use \"/\" or \".\"")
54+
viper.BindPFlag("provider.kubernetes.delimiter", startCmd.Flags().Lookup("provider.kubernetes.delimiter"))
5355
// Server flags
5456
startCmd.Flags().IntVar(&conf.Server.Port, "server.port", 10000, "The server port to use")
5557
viper.BindPFlag("server.port", startCmd.Flags().Lookup("server.port"))

cmd/root_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,32 @@ import (
1717
"gotest.tools/v3/assert"
1818
)
1919

20+
func TestDefault(t *testing.T) {
21+
testDir, err := os.Getwd()
22+
require.NoError(t, err, "error getting the current working directory")
23+
24+
wantConfig, err := ioutil.ReadFile(filepath.Join(testDir, "testdata", "config_default.json"))
25+
require.NoError(t, err, "error reading test config file")
26+
27+
// CHANGE `startCmd` behavior to only print the config, this is for testing purposes only
28+
newStartCommand = mockStartCommand
29+
30+
t.Run("config file", func(t *testing.T) {
31+
conf = config.NewConfig()
32+
cmd := NewRootCommand()
33+
output := &bytes.Buffer{}
34+
cmd.SetOut(output)
35+
cmd.SetArgs([]string{
36+
"start",
37+
})
38+
cmd.Execute()
39+
40+
gotOutput := output.String()
41+
42+
assert.Equal(t, string(wantConfig), gotOutput)
43+
})
44+
}
45+
2046
func TestPrecedence(t *testing.T) {
2147
testDir, err := os.Getwd()
2248
require.NoError(t, err, "error getting the current working directory")
@@ -82,6 +108,7 @@ func TestPrecedence(t *testing.T) {
82108
"--provider.name", "cli",
83109
"--provider.kubernetes.qps", "256",
84110
"--provider.kubernetes.burst", "512",
111+
"--provider.kubernetes.delimiter", "_",
85112
"--server.port", "3333",
86113
"--server.base-path", "/cli/",
87114
"--storage.file", "/tmp/cli.json",

cmd/testdata/config.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
PROVIDER_NAME=envvar
22
PROVIDER_KUBERNETES_QPS=16
33
PROVIDER_KUBERNETES_BURST=32
4+
PROVIDER_KUBERNETES_DELIMITER=/
45
SERVER_PORT=2222
56
SERVER_BASE_PATH=/envvar/
67
STORAGE_FILE=/tmp/envvar.json

cmd/testdata/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ provider:
33
kubernetes:
44
qps: 64
55
burst: 128
6+
delimiter: .
67
server:
78
port: 1111
89
base-path: /configfile/

cmd/testdata/config_cli_wanted.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"Name": "cli",
1111
"Kubernetes": {
1212
"QPS": 256,
13-
"Burst": 512
13+
"Burst": 512,
14+
"Delimiter": "_"
1415
}
1516
},
1617
"Sessions": {

cmd/testdata/config_default.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"Server": {
3+
"Port": 10000,
4+
"BasePath": "/"
5+
},
6+
"Storage": {
7+
"File": ""
8+
},
9+
"Provider": {
10+
"Name": "docker",
11+
"Kubernetes": {
12+
"QPS": 5,
13+
"Burst": 10,
14+
"Delimiter": "_"
15+
}
16+
},
17+
"Sessions": {
18+
"DefaultDuration": 300000000000,
19+
"ExpirationInterval": 20000000000
20+
},
21+
"Logging": {
22+
"Level": "info"
23+
},
24+
"Strategy": {
25+
"Dynamic": {
26+
"CustomThemesPath": "",
27+
"ShowDetailsByDefault": true,
28+
"DefaultTheme": "hacker-terminal",
29+
"DefaultRefreshFrequency": 5000000000
30+
},
31+
"Blocking": {
32+
"DefaultTimeout": 60000000000
33+
}
34+
}
35+
}

cmd/testdata/config_env_wanted.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"Name": "envvar",
1111
"Kubernetes": {
1212
"QPS": 16,
13-
"Burst": 32
13+
"Burst": 32,
14+
"Delimiter": "/"
1415
}
1516
},
1617
"Sessions": {

0 commit comments

Comments
 (0)