Skip to content

Commit 77602f4

Browse files
authored
Merge pull request #94 from axel7083/feature/keys-support
feat: support keys in valueFrom to filter secrets and global improvements
2 parents fcb98b9 + 3fe7da5 commit 77602f4

File tree

9 files changed

+366
-112
lines changed

9 files changed

+366
-112
lines changed

.github/workflows/e2e-testing.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ on: [push, pull_request]
55
jobs:
66
lint-test:
77
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
kind-node-images:
11+
- kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
12+
- kindest/node:v1.26.6@sha256:6e2d8b28a5b601defe327b98bd1c2d1930b49e5d8c512e1895099e4504007adb
13+
- kindest/node:v1.25.11@sha256:227fa11ce74ea76a0474eeefb84cb75d8dad1b08638371ecf0e86259b35be0c8
14+
- kindest/node:v1.24.15@sha256:7db4f8bea3e14b82d12e044e25e34bd53754b7f2b0e9d56df21774e6f66a70ab
15+
816
steps:
917
- name: Checkout
1018
uses: actions/checkout@v3
@@ -42,6 +50,8 @@ jobs:
4250

4351
- name: Create kind cluster
4452
uses: helm/[email protected]
53+
with:
54+
node_image: ${{ matrix.kind-node-images }}
4555

4656
- name: Loading locally build image to kind cluster
4757
run: kind load docker-image cluster-secret:${{ github.sha }} --name=chart-testing

README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# ClusterSecret
2-
![CI](https://github.com/zakkg3/ClusterSecret/workflows/CI/badge.svg) [![Docker Repository on Quay](https://quay.io/repository/clustersecret/clustersecret/status "Docker Repository on Quay")](https://quay.io/repository/clustersecret/clustersecret) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/clutersecret)](https://artifacthub.io/packages/search?repo=clutersecret) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4283/badge)](https://bestpractices.coreinfrastructure.org/projects/4283) [![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
3-
2+
![CI](https://github.com/zakkg3/ClusterSecret/workflows/CI/badge.svg) [![Docker Repository on Quay](https://quay.io/repository/clustersecret/clustersecret/status "Docker Repository on Quay")](https://quay.io/repository/clustersecret/clustersecret) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/clutersecret)](https://artifacthub.io/packages/search?repo=clutersecret) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4283/badge)](https://bestpractices.coreinfrastructure.org/projects/4283) [![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) [![Kubernetes - v1.24.15 | v1.25.11 | v1.26.6 | v1.27.3](https://img.shields.io/static/v1?label=Kubernetes&message=v1.24.15+|+v1.25.11+|+v1.26.6+|+v1.27.3&color=2ea44f)](https://)
43
---
54

65
## Kubernetes ClusterSecret

conformance/k8s_utils.py

+48-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import time
2-
from typing import Dict, Optional, List, Callable
2+
from typing import Dict, Optional, List, Callable, Any
33
from kubernetes import client, config
44
from kubernetes.client import V1Secret, CoreV1Api, CustomObjectsApi
55
from kubernetes.client.rest import ApiException
@@ -55,16 +55,57 @@ def __init__(self, custom_objects_api: CustomObjectsApi, api_instance: CoreV1Api
5555
self.retry_attempts = 3
5656
self.retry_delay = 5
5757

58+
def create_secret(
59+
self,
60+
name: str,
61+
namespace: str,
62+
data: Dict[str, Any],
63+
labels: Optional[Dict[str, str]] = None,
64+
annotations: Optional[Dict[str, str]] = None,
65+
):
66+
self.api_instance.create_namespaced_secret(
67+
namespace=namespace,
68+
body=client.V1Secret(
69+
metadata=client.V1ObjectMeta(
70+
name=name,
71+
labels=labels,
72+
annotations=annotations,
73+
),
74+
data=data,
75+
),
76+
)
77+
78+
@staticmethod
79+
def _generate_secret_key_ref_dict(secret_key_ref: Dict[str, str]) -> Dict[str, Any]:
80+
if secret_key_ref.get('name', None) is None or secret_key_ref.get('namespace', None) is None:
81+
raise Exception(f'secretKeyRef dict should have a name and a namespace property defined.')
82+
83+
return (
84+
{
85+
"valueFrom": {
86+
"secretKeyRef": {
87+
"name": secret_key_ref.get('name'),
88+
"namespace": secret_key_ref.get('namespace'),
89+
"keys": secret_key_ref.get('keys'),
90+
},
91+
},
92+
}
93+
)
94+
5895
def create_cluster_secret(
5996
self,
6097
name: str,
6198
namespace: str,
62-
data: Dict[str, str],
99+
data: Optional[Dict[str, Any]] = None,
100+
secret_key_ref: Optional[Dict[str, str]] = None,
63101
labels: Optional[Dict[str, str]] = None,
64102
annotations: Optional[Dict[str, str]] = None,
65103
match_namespace: Optional[List[str]] = None,
66104
avoid_namespaces: Optional[List[str]] = None,
67105
):
106+
if data is None and secret_key_ref is None:
107+
raise Exception('You need to either define data or secret_key_ref.')
108+
68109
return self.custom_objects_api.create_namespaced_custom_object(
69110
group="clustersecret.io",
70111
version="v1",
@@ -73,7 +114,7 @@ def create_cluster_secret(
73114
"apiVersion": "clustersecret.io/v1",
74115
"kind": "ClusterSecret",
75116
"metadata": {"name": name, "labels": labels, "annotations": annotations},
76-
"data": data,
117+
"data": data if data is not None else self._generate_secret_key_ref_dict(secret_key_ref),
77118
"matchNamespace": match_namespace,
78119
"avoidNamespaces": avoid_namespaces,
79120
},
@@ -169,3 +210,7 @@ def retry(self, f: Callable[[], bool]) -> bool:
169210
sleep(self.retry_delay)
170211
self.retry_attempts -= 1
171212
return False
213+
214+
def cleanup(self):
215+
# TODO: cleanup all secrets and cluster secrets created.
216+
pass

conformance/tests.py

+96-37
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import unittest
2-
32
from kubernetes import client, config
43
from kubernetes.client.rest import ApiException
54

@@ -17,6 +16,18 @@
1716

1817

1918
class ClusterSecretCases(unittest.TestCase):
19+
20+
def setUp(self) -> None:
21+
self.cluster_secret_manager = ClusterSecretManager(
22+
custom_objects_api=custom_objects_api,
23+
api_instance=api_instance
24+
)
25+
super().setUp()
26+
27+
def tearDown(self) -> None:
28+
self.cluster_secret_manager.cleanup()
29+
super().tearDown()
30+
2031
@classmethod
2132
def setUpClass(cls) -> None:
2233
# Wait for the cluster secret pod to be ready before running tests
@@ -33,6 +44,7 @@ def setUpClass(cls) -> None:
3344
print(f"Namespace '{namespace_name}' already exists.")
3445
else:
3546
print(f"Error creating namespace '{namespace_name}': {e}")
47+
3648
super().setUpClass()
3749

3850
def test_running(self):
@@ -42,20 +54,16 @@ def test_running(self):
4254
def test_simple_cluster_secret(self):
4355
name = "simple-cluster-secret"
4456
username_data = "MTIzNDU2Cg=="
45-
cluster_secret_manager = ClusterSecretManager(
46-
custom_objects_api=custom_objects_api,
47-
api_instance=api_instance
48-
)
4957

50-
cluster_secret_manager.create_cluster_secret(
58+
self.cluster_secret_manager.create_cluster_secret(
5159
name=name,
5260
namespace=USER_NAMESPACES[0],
5361
data={"username": username_data}
5462
)
5563

5664
# We expect the secret to be in ALL namespaces
5765
self.assertTrue(
58-
cluster_secret_manager.validate_namespace_secrets(
66+
self.cluster_secret_manager.validate_namespace_secrets(
5967
name=name,
6068
data={"username": username_data},
6169
)
@@ -64,13 +72,9 @@ def test_simple_cluster_secret(self):
6472
def test_complex_cluster_secret(self):
6573
name = "complex-cluster-secret"
6674
username_data = "MTIzNDU2Cg=="
67-
cluster_secret_manager = ClusterSecretManager(
68-
custom_objects_api=custom_objects_api,
69-
api_instance=api_instance
70-
)
7175

7276
# Create a secret in all user namespace expect the first one
73-
cluster_secret_manager.create_cluster_secret(
77+
self.cluster_secret_manager.create_cluster_secret(
7478
name=name,
7579
namespace=USER_NAMESPACES[0],
7680
data={"username": username_data},
@@ -80,7 +84,7 @@ def test_complex_cluster_secret(self):
8084

8185
# Ensure the secrets is only present where is to suppose to be
8286
self.assertTrue(
83-
cluster_secret_manager.validate_namespace_secrets(
87+
self.cluster_secret_manager.validate_namespace_secrets(
8488
name=name,
8589
data={"username": username_data},
8690
namespaces=USER_NAMESPACES[1:],
@@ -91,36 +95,32 @@ def test_patch_cluster_secret_data(self):
9195
name = "dynamic-cluster-secret"
9296
username_data = "MTIzNDU2Cg=="
9397
updated_data = "Nzg5MTAxMTIxMgo="
94-
cluster_secret_manager = ClusterSecretManager(
95-
custom_objects_api=custom_objects_api,
96-
api_instance=api_instance
97-
)
9898

9999
# Create a secret with username_data
100-
cluster_secret_manager.create_cluster_secret(
100+
self.cluster_secret_manager.create_cluster_secret(
101101
name=name,
102102
namespace=USER_NAMESPACES[0],
103103
data={"username": username_data},
104104
)
105105

106106
# Ensure the secret is created with the right data
107107
self.assertTrue(
108-
cluster_secret_manager.validate_namespace_secrets(
108+
self.cluster_secret_manager.validate_namespace_secrets(
109109
name=name,
110110
data={"username": username_data},
111111
)
112112
)
113113

114114
# Update the cluster secret's data
115-
cluster_secret_manager.update_data_cluster_secret(
115+
self.cluster_secret_manager.update_data_cluster_secret(
116116
name=name,
117117
data={"username": updated_data},
118118
namespace=USER_NAMESPACES[0],
119119
)
120120

121121
# Ensure the secrets are updated with the right data (at some point)
122122
self.assertTrue(
123-
cluster_secret_manager.validate_namespace_secrets(
123+
self.cluster_secret_manager.validate_namespace_secrets(
124124
name=name,
125125
data={"username": updated_data},
126126
),
@@ -130,12 +130,8 @@ def test_patch_cluster_secret_data(self):
130130
def test_patch_cluster_secret_match_namespaces(self):
131131
name = "dynamic-cluster-secret-match-namespaces"
132132
username_data = "MTIzNDU2Cg=="
133-
cluster_secret_manager = ClusterSecretManager(
134-
custom_objects_api=custom_objects_api,
135-
api_instance=api_instance
136-
)
137133

138-
cluster_secret_manager.create_cluster_secret(
134+
self.cluster_secret_manager.create_cluster_secret(
139135
name=name,
140136
namespace=USER_NAMESPACES[0],
141137
data={"username": username_data},
@@ -145,7 +141,7 @@ def test_patch_cluster_secret_match_namespaces(self):
145141
)
146142

147143
self.assertTrue(
148-
cluster_secret_manager.validate_namespace_secrets(
144+
self.cluster_secret_manager.validate_namespace_secrets(
149145
name=name,
150146
data={"username": username_data},
151147
namespaces=[
@@ -156,15 +152,15 @@ def test_patch_cluster_secret_match_namespaces(self):
156152
)
157153

158154
# Update the cluster match_namespace to ALL user namespace
159-
cluster_secret_manager.update_data_cluster_secret(
155+
self.cluster_secret_manager.update_data_cluster_secret(
160156
name=name,
161157
namespace=USER_NAMESPACES[0],
162158
match_namespace=USER_NAMESPACES,
163159
data={"username": username_data},
164160
)
165161

166162
self.assertTrue(
167-
cluster_secret_manager.validate_namespace_secrets(
163+
self.cluster_secret_manager.validate_namespace_secrets(
168164
name=name,
169165
data={"username": username_data},
170166
namespaces=USER_NAMESPACES,
@@ -175,40 +171,103 @@ def test_patch_cluster_secret_match_namespaces(self):
175171
def test_simple_cluster_secret_deleted(self):
176172
name = "simple-cluster-secret-deleted"
177173
username_data = "MTIzNDU2Cg=="
178-
cluster_secret_manager = ClusterSecretManager(
179-
custom_objects_api=custom_objects_api,
180-
api_instance=api_instance
181-
)
182174

183-
cluster_secret_manager.create_cluster_secret(
175+
self.cluster_secret_manager.create_cluster_secret(
184176
name=name,
185177
namespace=USER_NAMESPACES[0],
186178
data={"username": username_data}
187179
)
188180

189181
# We expect the secret to be in ALL namespaces
190182
self.assertTrue(
191-
cluster_secret_manager.validate_namespace_secrets(
183+
self.cluster_secret_manager.validate_namespace_secrets(
192184
name=name,
193185
data={"username": username_data}
194186
)
195187
)
196188

197-
cluster_secret_manager.delete_cluster_secret(
189+
self.cluster_secret_manager.delete_cluster_secret(
198190
name=name,
199191
namespace=USER_NAMESPACES[0],
200192
)
201193

202194
# We expect the secret to be in NO namespaces
203195
self.assertTrue(
204-
cluster_secret_manager.validate_namespace_secrets(
196+
self.cluster_secret_manager.validate_namespace_secrets(
205197
name=name,
206198
data={"username": username_data},
207199
namespaces=[],
208200
),
209201
f'secret {name} should be deleted from all namespaces.'
210202
)
211203

204+
def test_value_from_cluster_secret(self):
205+
cluster_secret_name = "value-from-cluster-secret"
206+
secret_name = "basic-secret-example"
207+
208+
username_data = "MTIzNDU2Cg=="
209+
210+
# Create a kubernetes secrets
211+
self.cluster_secret_manager.create_secret(
212+
name=secret_name,
213+
namespace=USER_NAMESPACES[0],
214+
data={'username': username_data}
215+
)
216+
217+
# Create the cluster secret
218+
self.cluster_secret_manager.create_cluster_secret(
219+
name=cluster_secret_name,
220+
namespace=USER_NAMESPACES[0],
221+
secret_key_ref={
222+
'name': secret_name,
223+
'namespace': USER_NAMESPACES[0],
224+
},
225+
)
226+
227+
# We expect the secret to be in ALL namespaces
228+
self.assertTrue(
229+
self.cluster_secret_manager.validate_namespace_secrets(
230+
name=cluster_secret_name,
231+
data={"username": username_data},
232+
),
233+
msg=f'Cluster secret should take the data from the {secret_name} secret.'
234+
)
235+
236+
def test_value_from_with_keys_cluster_secret(self):
237+
cluster_secret_name = "value-from-with-keys-cluster-secret"
238+
secret_name = "k8s-basic-secret-example"
239+
240+
username_data = "MTIzNDU2Cg=="
241+
password_data = "aGloaXBhc3M="
242+
more_data = "aWlpaWlhYWE="
243+
244+
# Create a kubernetes secrets
245+
self.cluster_secret_manager.create_secret(
246+
name=secret_name,
247+
namespace=USER_NAMESPACES[0],
248+
data={'username': username_data, 'password': password_data, 'more-data': more_data}
249+
)
250+
251+
# Create the cluster secret
252+
self.cluster_secret_manager.create_cluster_secret(
253+
name=cluster_secret_name,
254+
namespace=USER_NAMESPACES[0],
255+
secret_key_ref={
256+
'name': secret_name,
257+
'namespace': USER_NAMESPACES[0],
258+
'keys': ['username', 'password']
259+
},
260+
)
261+
262+
# We expect the secret to be in ALL namespaces
263+
self.assertTrue(
264+
self.cluster_secret_manager.validate_namespace_secrets(
265+
name=cluster_secret_name,
266+
data={'username': username_data, 'password': password_data},
267+
),
268+
msg=f'Cluster secret should take the data from the {secret_name} secret but only the keys specified.'
269+
)
270+
212271

213272
if __name__ == '__main__':
214273
unittest.main()

0 commit comments

Comments
 (0)