Skip to content

Commit 371dfba

Browse files
committed
chore: apply PR feedback from initial review
- added helper function for authPolicies - secretBase is now not exported - added helper functions to construct SecretCreate and SecretUpdate - SecretValue now a pointer - mock data defined in repository layer Signed-off-by: Andy Stoneberg <[email protected]>
1 parent 32c89be commit 371dfba

File tree

6 files changed

+382
-306
lines changed

6 files changed

+382
-306
lines changed

workspaces/backend/api/secrets_handler.go

Lines changed: 69 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ limitations under the License.
1717
package api
1818

1919
import (
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

3535
type SecretEnvelope Envelope[*models.SecretUpdate]
3636
type SecretListEnvelope Envelope[[]models.SecretListItem]
3737
type 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-
CreatedBy: "[email protected]",
104-
UpdatedAt: time.Date(2024, 2, 20, 14, 45, 0, 0, time.UTC),
105-
UpdatedBy: "[email protected]",
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-
CreatedBy: "[email protected]",
120-
UpdatedAt: time.Date(2024, 1, 10, 9, 15, 0, 0, time.UTC),
121-
UpdatedBy: "[email protected]",
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-
CreatedBy: "[email protected]",
136-
UpdatedAt: time.Date(2024, 3, 12, 11, 30, 0, 0, time.UTC),
137-
UpdatedBy: "[email protected]",
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

Comments
 (0)