@@ -17,9 +17,9 @@ limitations under the License.
1717package api
1818
1919import (
20+ "errors"
2021 "fmt"
2122 "net/http"
22- "time"
2323
2424 "github.com/julienschmidt/httprouter"
2525 corev1 "k8s.io/api/core/v1"
@@ -28,14 +28,28 @@ import (
2828
2929 "github.com/kubeflow/notebooks/workspaces/backend/internal/auth"
3030 "github.com/kubeflow/notebooks/workspaces/backend/internal/helper"
31- "github.com/kubeflow/notebooks/workspaces/backend/internal/models/common"
3231 models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/secrets"
32+ repository "github.com/kubeflow/notebooks/workspaces/backend/internal/repositories/secrets"
3333)
3434
3535type SecretEnvelope Envelope [* models.SecretUpdate ]
3636type SecretListEnvelope Envelope [[]models.SecretListItem ]
3737type SecretCreateEnvelope Envelope [* models.SecretCreate ]
3838
39+ // getSecretAuthPolicies creates authorization policies for secret operations.
40+ func getSecretAuthPolicies (verb auth.ResourceVerb , namespace string ) []* auth.ResourcePolicy {
41+ return []* auth.ResourcePolicy {
42+ auth .NewResourcePolicy (
43+ verb ,
44+ & corev1.Secret {
45+ ObjectMeta : metav1.ObjectMeta {
46+ Namespace : namespace ,
47+ },
48+ },
49+ ),
50+ }
51+ }
52+
3953// GetSecretsHandler returns a list of all secrets in a namespace.
4054//
4155// @Summary Returns a list of all secrets in a namespace
@@ -62,84 +76,22 @@ func (a *App) GetSecretsHandler(w http.ResponseWriter, r *http.Request, ps httpr
6276 }
6377
6478 // =========================== AUTH ===========================
65- authPolicies := []* auth.ResourcePolicy {
66- auth .NewResourcePolicy (
67- auth .ResourceVerbList ,
68- & corev1.Secret {
69- ObjectMeta : metav1.ObjectMeta {
70- Namespace : namespace ,
71- },
72- },
73- ),
74- }
79+ authPolicies := getSecretAuthPolicies (auth .ResourceVerbList , namespace )
7580 if success := a .requireAuth (w , r , authPolicies ); ! success {
7681 return
7782 }
7883 // ============================================================
7984
80- // TODO: Replace with actual repository call when implemented
81- // For now, return dummy data as stub
82- secretList := getMockSecrets ()
85+ secretList , err := a .repositories .Secret .GetSecrets (r .Context (), namespace )
86+ if err != nil {
87+ a .serverErrorResponse (w , r , err )
88+ return
89+ }
90+
8391 responseEnvelope := & SecretListEnvelope {Data : secretList }
8492 a .dataResponse (w , r , responseEnvelope )
8593}
8694
87- // getMockSecrets returns temporary mock data for frontend development
88- // TODO: Remove this function when actual repository implementation is ready
89- func getMockSecrets () []models.SecretListItem {
90- return []models.SecretListItem {
91- {
92- Name : "database-credentials" ,
93- Type : "Opaque" ,
94- Immutable : false ,
95- CanUpdate : true ,
96- CanMount : true ,
97- Mounts : []models.SecretMount {
98- {Group : "apps" , Kind : "Deployment" , Name : "web-app" },
99- {Group : "apps" , Kind : "Deployment" , Name : "api-server" },
100- },
101- Audit : common.Audit {
102- CreatedAt : time .Date (2024 , 1 , 15 , 10 , 30 , 0 , 0 , time .UTC ),
103- 104- UpdatedAt : time .Date (2024 , 2 , 20 , 14 , 45 , 0 , 0 , time .UTC ),
105- 106- },
107- },
108- {
109- Name : "api-key-secret" ,
110- Type : "Opaque" ,
111- Immutable : true ,
112- CanUpdate : false ,
113- CanMount : true ,
114- Mounts : []models.SecretMount {
115- {Group : "apps" , Kind : "Deployment" , Name : "external-api-client" },
116- },
117- Audit : common.Audit {
118- CreatedAt : time .Date (2024 , 1 , 10 , 9 , 15 , 0 , 0 , time .UTC ),
119- 120- UpdatedAt : time .Date (2024 , 1 , 10 , 9 , 15 , 0 , 0 , time .UTC ),
121- 122- },
123- },
124- {
125- Name : "tls-certificate" ,
126- Type : "kubernetes.io/tls" ,
127- Immutable : false ,
128- CanUpdate : false ,
129- CanMount : true ,
130- Mounts : []models.SecretMount {
131- {Group : "networking.k8s.io" , Kind : "Ingress" , Name : "web-ingress" },
132- },
133- Audit : common.Audit {
134- CreatedAt : time .Date (2024 , 3 , 5 , 16 , 20 , 0 , 0 , time .UTC ),
135- 136- UpdatedAt : time .Date (2024 , 3 , 12 , 11 , 30 , 0 , 0 , time .UTC ),
137- 138- },
139- },
140- }
141- }
142-
14395// GetSecretHandler returns a specific secret by name and namespace.
14496//
14597// @Summary Returns a specific secret
@@ -170,124 +122,27 @@ func (a *App) GetSecretHandler(w http.ResponseWriter, r *http.Request, ps httpro
170122 }
171123
172124 // =========================== AUTH ===========================
173- authPolicies := []* auth.ResourcePolicy {
174- auth .NewResourcePolicy (
175- auth .ResourceVerbGet ,
176- & corev1.Secret {
177- ObjectMeta : metav1.ObjectMeta {
178- Namespace : namespace ,
179- },
180- },
181- ),
182- }
125+ authPolicies := getSecretAuthPolicies (auth .ResourceVerbGet , namespace )
183126 if success := a .requireAuth (w , r , authPolicies ); ! success {
184127 return
185128 }
186129 // ============================================================
187130
188- // TODO: Replace with actual repository call when implemented
189- // For now, return mock data as stub
190- secret := getMockSecret (secretName )
191- if secret == nil {
192- a .notFoundResponse (w , r )
131+ secret , err := a .repositories .Secret .GetSecret (r .Context (), namespace , secretName )
132+ if err != nil {
133+ // Check if it's a not found error
134+ if errors .Is (err , repository .ErrSecretNotFound ) {
135+ a .notFoundResponse (w , r )
136+ return
137+ }
138+ a .serverErrorResponse (w , r , err )
193139 return
194140 }
141+
195142 responseEnvelope := & SecretEnvelope {Data : secret }
196143 a .dataResponse (w , r , responseEnvelope )
197144}
198145
199- // getMockSecret returns temporary mock data for a specific secret by name
200- // TODO: Remove this function when actual repository implementation is ready
201- func getMockSecret (secretName string ) * models.SecretUpdate {
202- switch secretName {
203- case "database-credentials" :
204- return & models.SecretUpdate {
205- SecretBase : models.SecretBase {
206- Type : "Opaque" ,
207- Immutable : false ,
208- Contents : models.SecretData {
209- "username" : models.SecretValue {},
210- "password" : models.SecretValue {},
211- "host" : models.SecretValue {},
212- "port" : models.SecretValue {},
213- },
214- },
215- }
216- case "api-key-secret" :
217- return & models.SecretUpdate {
218- SecretBase : models.SecretBase {
219- Type : "Opaque" ,
220- Immutable : true ,
221- Contents : models.SecretData {
222- "api-key" : models.SecretValue {},
223- "api-secret" : models.SecretValue {},
224- },
225- },
226- }
227- case "tls-certificate" :
228- return & models.SecretUpdate {
229- SecretBase : models.SecretBase {
230- Type : "kubernetes.io/tls" ,
231- Immutable : false ,
232- Contents : models.SecretData {
233- "tls.crt" : models.SecretValue {},
234- "tls.key" : models.SecretValue {},
235- },
236- },
237- }
238- default :
239- return nil // Return nil for unknown secret names to trigger 404
240- }
241- }
242-
243- // createMockSecretFromRequest returns temporary mock data based on the create request
244- // TODO: Remove this function when actual repository implementation is ready
245- func createMockSecretFromRequest (secretCreate * models.SecretCreate ) * models.SecretCreate {
246- // Create empty contents to never expose actual secret values
247- contents := make (models.SecretData )
248- for key := range secretCreate .Contents {
249- contents [key ] = models.SecretValue {} // Empty value - never return actual data
250- }
251-
252- // Use the request data to create a mock response
253- // This simulates what would happen after creating the secret
254- return & models.SecretCreate {
255- Name : secretCreate .Name ,
256- SecretBase : models.SecretBase {
257- Type : secretCreate .Type ,
258- Immutable : secretCreate .Immutable ,
259- Contents : contents ,
260- },
261- }
262- }
263-
264- // updateMockSecretFromRequest returns temporary mock data based on the update request
265- // TODO: Remove this function when actual repository implementation is ready
266- func updateMockSecretFromRequest (secretName string , secretUpdate * models.SecretUpdate ) * models.SecretUpdate {
267- // Check if the secret exists in our mock data
268- switch secretName {
269- case "database-credentials" , "api-key-secret" , "tls-certificate" :
270-
271- // Create empty contents to never expose actual secret values
272- contents := make (models.SecretData )
273- for key := range secretUpdate .Contents {
274- contents [key ] = models.SecretValue {} // Empty value - never return actual data
275- }
276-
277- // Return the updated secret data (simulating successful update)
278- return & models.SecretUpdate {
279- SecretBase : models.SecretBase {
280- Type : secretUpdate .Type ,
281- Immutable : secretUpdate .Immutable ,
282- Contents : contents ,
283- },
284- }
285- default :
286- // Return nil for unknown secret names to trigger 404
287- return nil
288- }
289- }
290-
291146// CreateSecretHandler creates a new secret.
292147//
293148// @Summary Creates a new secret
@@ -320,16 +175,7 @@ func (a *App) CreateSecretHandler(w http.ResponseWriter, r *http.Request, ps htt
320175 }
321176
322177 // =========================== AUTH ===========================
323- authPolicies := []* auth.ResourcePolicy {
324- auth .NewResourcePolicy (
325- auth .ResourceVerbCreate ,
326- & corev1.Secret {
327- ObjectMeta : metav1.ObjectMeta {
328- Namespace : namespace ,
329- },
330- },
331- ),
332- }
178+ authPolicies := getSecretAuthPolicies (auth .ResourceVerbCreate , namespace )
333179 if success := a .requireAuth (w , r , authPolicies ); ! success {
334180 return
335181 }
@@ -360,11 +206,20 @@ func (a *App) CreateSecretHandler(w http.ResponseWriter, r *http.Request, ps htt
360206 return
361207 }
362208
363- // TODO: Replace with actual repository call when implemented
364- // For now, return mock data as stub
365- secret := createMockSecretFromRequest (bodyEnvelope .Data )
209+ secret , err := a .repositories .Secret .CreateSecret (r .Context (), namespace , bodyEnvelope .Data )
210+ if err != nil {
211+ // Check if it's an already exists error
212+ if errors .Is (err , repository .ErrSecretAlreadyExists ) {
213+ causes := helper .StatusCausesFromAPIStatus (err )
214+ a .conflictResponse (w , r , err , causes )
215+ return
216+ }
217+ a .serverErrorResponse (w , r , err )
218+ return
219+ }
220+
366221 responseEnvelope := & SecretCreateEnvelope {Data : secret }
367- location := fmt .Sprintf ("/secrets/%s/%s" , namespace , bodyEnvelope . Data .Name )
222+ location := fmt .Sprintf ("/secrets/%s/%s" , namespace , secret .Name )
368223 a .createdResponse (w , r , responseEnvelope , location )
369224}
370225
@@ -403,16 +258,7 @@ func (a *App) UpdateSecretHandler(w http.ResponseWriter, r *http.Request, ps htt
403258 }
404259
405260 // =========================== AUTH ===========================
406- authPolicies := []* auth.ResourcePolicy {
407- auth .NewResourcePolicy (
408- auth .ResourceVerbUpdate ,
409- & corev1.Secret {
410- ObjectMeta : metav1.ObjectMeta {
411- Namespace : namespace ,
412- },
413- },
414- ),
415- }
261+ authPolicies := getSecretAuthPolicies (auth .ResourceVerbUpdate , namespace )
416262 if success := a .requireAuth (w , r , authPolicies ); ! success {
417263 return
418264 }
@@ -443,13 +289,17 @@ func (a *App) UpdateSecretHandler(w http.ResponseWriter, r *http.Request, ps htt
443289 return
444290 }
445291
446- // TODO: Replace with actual repository call when implemented
447- // For now, return mock data as stub
448- secret := updateMockSecretFromRequest (secretName , bodyEnvelope .Data )
449- if secret == nil {
450- a .notFoundResponse (w , r )
292+ secret , err := a .repositories .Secret .UpdateSecret (r .Context (), namespace , secretName , bodyEnvelope .Data )
293+ if err != nil {
294+ // Check if it's a not found error
295+ if errors .Is (err , repository .ErrSecretNotFound ) {
296+ a .notFoundResponse (w , r )
297+ return
298+ }
299+ a .serverErrorResponse (w , r , err )
451300 return
452301 }
302+
453303 responseEnvelope := & SecretEnvelope {Data : secret }
454304 a .dataResponse (w , r , responseEnvelope )
455305}
@@ -483,22 +333,22 @@ func (a *App) DeleteSecretHandler(w http.ResponseWriter, r *http.Request, ps htt
483333 }
484334
485335 // =========================== AUTH ===========================
486- authPolicies := []* auth.ResourcePolicy {
487- auth .NewResourcePolicy (
488- auth .ResourceVerbDelete ,
489- & corev1.Secret {
490- ObjectMeta : metav1.ObjectMeta {
491- Namespace : namespace ,
492- },
493- },
494- ),
495- }
336+ authPolicies := getSecretAuthPolicies (auth .ResourceVerbDelete , namespace )
496337 if success := a .requireAuth (w , r , authPolicies ); ! success {
497338 return
498339 }
499340 // ============================================================
500341
501- // TODO: Replace with actual repository call when implemented
502- // For now, always return 204 No Content as stub
342+ err := a .repositories .Secret .DeleteSecret (r .Context (), namespace , secretName )
343+ if err != nil {
344+ // Check if it's a not found error
345+ if errors .Is (err , repository .ErrSecretNotFound ) {
346+ a .notFoundResponse (w , r )
347+ return
348+ }
349+ a .serverErrorResponse (w , r , err )
350+ return
351+ }
352+
503353 a .deletedResponse (w , r )
504354}
0 commit comments