Skip to content

Commit 3c837d3

Browse files
committed
fix(ws): add secrets attribute to backend API schema
related: kubeflow#239 This commit brings partial support for secrets to the backend API. It enables the `frontend` component to successfully create a Workspace through the "wizard flow". **HOWEVER**, it is important to note this secrets attribute is not supported within the `controller` component yet - as kubeflow#240 is not yet merged. To unblock the `frontend` - the logic contained in this commit simply adds the necessary scaffolding to accept the `secrets` attribute defined within `volumes`. Once umarshalled, the backend essentially ignores this data. Code to fully enable the end to end flow is included in this PR - but simply commented out with `TODO:` comments denoting what can be uncommented once kubeflow#240 is merged into `notebooks-v2`. A test is also presently disabled with `XIt` - and can also be enabled when required code present. Changes were initially coded against the branch provided on kubeflow#240 to verify full end-to-end behavior. Once confirmed, commit was rebased onto `notebooks-v2`, relevant code commented out as described above, and behavior retested to ensure desired outcome. In summary, with these changes: - `backend` API accepts `volumes.secrets` in the _Create_ payload - secrets data is **NOT USED** when programmatically constructing the Workspace CR - Resultant workspace has no `secrets` data - irrespective of it if was provided in the payload or not. Signed-off-by: Andy Stoneberg <[email protected]>
1 parent aa45741 commit 3c837d3

File tree

4 files changed

+110
-4
lines changed

4 files changed

+110
-4
lines changed

workspaces/backend/api/workspaces_handler_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,9 @@ var _ = Describe("Workspaces Handler", func() {
770770
}
771771
Expect(createdWorkspace.Spec.PodTemplate.Volumes.Data).To(Equal(expected))
772772

773+
// TODO: Verify secrets once #240 merged
774+
// Expect(createdWorkspace.Spec.PodTemplate.Volumes.Secrets).To(BeEmpty())
775+
773776
By("creating an HTTP request to delete the Workspace")
774777
path = strings.Replace(WorkspacesByNamePath, ":"+NamespacePathParam, namespaceNameCrud, 1)
775778
path = strings.Replace(path, ":"+ResourceNamePathParam, workspaceName, 1)
@@ -805,6 +808,70 @@ var _ = Describe("Workspaces Handler", func() {
805808
Expect(apierrors.IsNotFound(err)).To(BeTrue())
806809
})
807810

811+
// TODO: Change to It when #240 merged
812+
XIt("should create a workspace with secrets", func() {
813+
// Create a workspace with secrets
814+
workspace := &models.WorkspaceCreate{
815+
Name: "test-workspace",
816+
Kind: "test-kind",
817+
PodTemplate: models.PodTemplateMutate{
818+
Options: models.PodTemplateOptionsMutate{
819+
ImageConfig: "test-image",
820+
PodConfig: "test-config",
821+
},
822+
Volumes: models.PodVolumesMutate{
823+
Data: []models.PodVolumeMount{
824+
{
825+
PVCName: "test-pvc",
826+
MountPath: "/data",
827+
},
828+
},
829+
Secrets: []models.PodSecretMount{
830+
{
831+
SecretName: "test-secret",
832+
MountPath: "/secrets",
833+
DefaultMode: int32(0o644),
834+
},
835+
},
836+
},
837+
},
838+
}
839+
840+
// Create the workspace using the API handler
841+
bodyEnvelope := WorkspaceCreateEnvelope{Data: workspace}
842+
bodyEnvelopeJSON, err := json.Marshal(bodyEnvelope)
843+
Expect(err).NotTo(HaveOccurred())
844+
845+
path := strings.Replace(WorkspacesByNamespacePath, ":"+NamespacePathParam, namespaceNameCrud, 1)
846+
req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyEnvelopeJSON)))
847+
Expect(err).NotTo(HaveOccurred())
848+
req.Header.Set("Content-Type", "application/json")
849+
req.Header.Set(userIdHeader, adminUser)
850+
851+
rr := httptest.NewRecorder()
852+
ps := httprouter.Params{
853+
httprouter.Param{
854+
Key: NamespacePathParam,
855+
Value: namespaceNameCrud,
856+
},
857+
}
858+
a.CreateWorkspaceHandler(rr, req, ps)
859+
rs := rr.Result()
860+
defer rs.Body.Close()
861+
862+
Expect(rs.StatusCode).To(Equal(http.StatusCreated), descUnexpectedHTTPStatus, rr.Body.String())
863+
864+
// Get the created workspace
865+
createdWorkspace := &kubefloworgv1beta1.Workspace{}
866+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: workspace.Name, Namespace: namespaceNameCrud}, createdWorkspace)).To(Succeed())
867+
868+
// TODO: Verify the secrets are properly set once #240 merged
869+
// Expect(createdWorkspace.Spec.PodTemplate.Volumes.Secrets).To(HaveLen(1))
870+
// Expect(createdWorkspace.Spec.PodTemplate.Volumes.Secrets[0].SecretName).To(Equal("test-secret"))
871+
// Expect(createdWorkspace.Spec.PodTemplate.Volumes.Secrets[0].MountPath).To(Equal("/secrets"))
872+
// Expect(createdWorkspace.Spec.PodTemplate.Volumes.Secrets[0].DefaultMode).To(Equal(int32(0o644)))
873+
})
874+
808875
// TODO: test when fail to create a Workspace when:
809876
// - body payload invalid (missing name/kind, and/or non RCF 1123 name)
810877
// - invalid namespace HTTP path parameter (also test for other API handlers)

workspaces/backend/internal/models/workspaces/types.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ type PodMetadata struct {
6767
}
6868

6969
type PodVolumes struct {
70-
Home *PodVolumeInfo `json:"home,omitempty"`
71-
Data []PodVolumeInfo `json:"data"`
70+
Home *PodVolumeInfo `json:"home,omitempty"`
71+
Data []PodVolumeInfo `json:"data"`
72+
Secrets []PodSecretInfo `json:"secrets,omitempty"`
7273
}
7374

7475
type PodVolumeInfo struct {
@@ -77,6 +78,12 @@ type PodVolumeInfo struct {
7778
ReadOnly bool `json:"readOnly"`
7879
}
7980

81+
type PodSecretInfo struct {
82+
SecretName string `json:"secretName"`
83+
MountPath string `json:"mountPath"`
84+
DefaultMode int32 `json:"defaultMode,omitempty"`
85+
}
86+
8087
type PodTemplateOptions struct {
8188
ImageConfig ImageConfig `json:"imageConfig"`
8289
PodConfig PodConfig `json:"podConfig"`

workspaces/backend/internal/models/workspaces/types_create.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ type PodMetadataMutate struct {
4343
}
4444

4545
type PodVolumesMutate struct {
46-
Home *string `json:"home,omitempty"`
47-
Data []PodVolumeMount `json:"data"`
46+
Home *string `json:"home,omitempty"`
47+
Data []PodVolumeMount `json:"data"`
48+
Secrets []PodSecretMount `json:"secrets,omitempty"`
4849
}
4950

5051
type PodVolumeMount struct {
@@ -53,6 +54,12 @@ type PodVolumeMount struct {
5354
ReadOnly bool `json:"readOnly,omitempty"`
5455
}
5556

57+
type PodSecretMount struct {
58+
SecretName string `json:"secretName"`
59+
MountPath string `json:"mountPath"`
60+
DefaultMode int32 `json:"defaultMode,omitempty"`
61+
}
62+
5663
type PodTemplateOptionsMutate struct {
5764
ImageConfig string `json:"imageConfig"`
5865
PodConfig string `json:"podConfig"`
@@ -87,5 +94,18 @@ func (w *WorkspaceCreate) Validate(prefix *field.Path) []*field.Error {
8794
errs = append(errs, helper.ValidateFieldIsNotEmpty(volumePath.Child("mountPath"), volume.MountPath)...)
8895
}
8996

97+
// validate the secrets
98+
secretsPath := prefix.Child("podTemplate", "volumes", "secrets")
99+
for i, secret := range w.PodTemplate.Volumes.Secrets {
100+
secretPath := secretsPath.Index(i)
101+
errs = append(errs, helper.ValidateFieldIsNotEmpty(secretPath.Child("secretName"), secret.SecretName)...)
102+
errs = append(errs, helper.ValidateFieldIsNotEmpty(secretPath.Child("mountPath"), secret.MountPath)...)
103+
if secret.DefaultMode != 0 {
104+
if secret.DefaultMode < 0 || secret.DefaultMode > 511 {
105+
errs = append(errs, field.Invalid(secretPath.Child("defaultMode"), secret.DefaultMode, "defaultMode must be between 0 and 511"))
106+
}
107+
}
108+
}
109+
90110
return errs
91111
}

workspaces/backend/internal/repositories/workspaces/repo.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ func (r *WorkspaceRepository) CreateWorkspace(ctx context.Context, workspaceCrea
137137
}
138138
}
139139

140+
// get secrets from workspace model
141+
// TODO: uncomment this once #240 is merged
142+
// secretMounts := make([]kubefloworgv1beta1.PodSecretMount, len(workspaceCreate.PodTemplate.Volumes.Secrets))
143+
// for i, secret := range workspaceCreate.PodTemplate.Volumes.Secrets {
144+
// secretMounts[i] = kubefloworgv1beta1.PodSecretMount{
145+
// SecretName: secret.SecretName,
146+
// MountPath: secret.MountPath,
147+
// DefaultMode: secret.DefaultMode,
148+
// }
149+
// }
150+
140151
// define workspace object from model
141152
workspaceName := workspaceCreate.Name
142153
workspaceKindName := workspaceCreate.Kind
@@ -157,6 +168,7 @@ func (r *WorkspaceRepository) CreateWorkspace(ctx context.Context, workspaceCrea
157168
Volumes: kubefloworgv1beta1.WorkspacePodVolumes{
158169
Home: workspaceCreate.PodTemplate.Volumes.Home,
159170
Data: dataVolumeMounts,
171+
// Secrets: secretMounts,
160172
},
161173
Options: kubefloworgv1beta1.WorkspacePodOptions{
162174
ImageConfig: workspaceCreate.PodTemplate.Options.ImageConfig,

0 commit comments

Comments
 (0)