diff --git a/access.go b/access.go index 34f7e308..04c540e5 100644 --- a/access.go +++ b/access.go @@ -57,7 +57,11 @@ func (s *server) checkAccess(subjectAccessReviewTemplate authorizationv1.Subject } // Update the SubjectAccessReview request with the namespace and user information - sar.Spec.ResourceAttributes.Namespace = vars["namespace"] + if namespace, ok := vars["namespace"]; ok { + sar.Spec.ResourceAttributes.Namespace = namespace + } else { + sar.Spec.ResourceAttributes.Namespace = "" + } sar.Spec.User = user // Submit the SubjectAccessReview to the Kubernetes API server diff --git a/listers.go b/listers.go index 868bad44..bf3bd461 100644 --- a/listers.go +++ b/listers.go @@ -15,40 +15,37 @@ func (s *server) setupListers(ctx context.Context) error { factory := informers.NewSharedInformerFactory(s.clientsets.kubernetes, 5*time.Minute) kubeflowFactory := kubeflowinformers.NewSharedInformerFactory(s.clientsets.kubeflow, time.Minute*5) + // Namespaces + namespacesInformer := factory.Core().V1().Namespaces() + s.listers.namespaces = namespacesInformer.Lister() + // Events eventsInformer := factory.Core().V1().Events() - go eventsInformer.Informer().Run(ctx.Done()) - s.listers.events = eventsInformer.Lister() // StorageClasses storageClassesInformer := factory.Storage().V1().StorageClasses() - go storageClassesInformer.Informer().Run(ctx.Done()) - s.listers.storageClasses = storageClassesInformer.Lister() // PersistentVolumeClaims pvcInformer := factory.Core().V1().PersistentVolumeClaims() - go pvcInformer.Informer().Run(ctx.Done()) - s.listers.persistentVolumeClaims = pvcInformer.Lister() // PodDefaults podDefaultsInformer := kubeflowFactory.Kubeflow().V1alpha1().PodDefaults() - go podDefaultsInformer.Informer().Run(ctx.Done()) - s.listers.podDefaults = podDefaultsInformer.Lister() // Notebooks notebooksInformer := kubeflowFactory.Kubeflow().V1().Notebooks() - go notebooksInformer.Informer().Run(ctx.Done()) - s.listers.notebooks = notebooksInformer.Lister() + go factory.Start(ctx.Done()) + go kubeflowFactory.Start(ctx.Done()) + // Wait until sync log.Printf("synching caches...") tctx, _ := context.WithTimeout(ctx, time.Minute) - if !cache.WaitForCacheSync(tctx.Done(), eventsInformer.Informer().HasSynced, storageClassesInformer.Informer().HasSynced, pvcInformer.Informer().HasSynced, podDefaultsInformer.Informer().HasSynced, notebooksInformer.Informer().HasSynced) { + if !cache.WaitForCacheSync(tctx.Done(), namespacesInformer.Informer().HasSynced, eventsInformer.Informer().HasSynced, storageClassesInformer.Informer().HasSynced, pvcInformer.Informer().HasSynced, podDefaultsInformer.Informer().HasSynced, notebooksInformer.Informer().HasSynced) { return fmt.Errorf("timeout synching caches") } log.Printf("done synching caches") diff --git a/main.go b/main.go index e19f7f0c..5e3f4e4c 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ var userIDHeader string var staticDirectory string type listers struct { + namespaces v1listers.NamespaceLister events v1listers.EventLister storageClasses storagev1listers.StorageClassLister persistentVolumeClaims v1listers.PersistentVolumeClaimLister @@ -117,8 +118,19 @@ func main() { // Setup route handlers router.HandleFunc("/api/config", s.GetConfig).Methods("GET") + router.HandleFunc("/api/storageclasses/default", s.GetDefaultStorageClass).Methods("GET") + router.HandleFunc("/api/namespaces", s.checkAccess(authorizationv1.SubjectAccessReview{ + Spec: authorizationv1.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Group: corev1.SchemeGroupVersion.Group, + Verb: "list", + Resource: "namespaces", + Version: corev1.SchemeGroupVersion.Version, + }, + }, + }, s.GetNamespaces)).Methods("GET") router.HandleFunc("/api/namespaces/{namespace}/notebooks", s.checkAccess(authorizationv1.SubjectAccessReview{ Spec: authorizationv1.SubjectAccessReviewSpec{ ResourceAttributes: &authorizationv1.ResourceAttributes{ diff --git a/namespaces.go b/namespaces.go new file mode 100644 index 00000000..acdaca49 --- /dev/null +++ b/namespaces.go @@ -0,0 +1,40 @@ +package main + +import ( + "log" + "net/http" + "sort" + + "k8s.io/apimachinery/pkg/labels" +) + +type namespacesresponse struct { + APIResponse + Namespaces []string `json:"namespaces"` +} + +// GetNamespaces returns the namespaces in the environment. +func (s *server) GetNamespaces(w http.ResponseWriter, r *http.Request) { + log.Printf("loading namespaces") + + namespaces, err := s.listers.namespaces.List(labels.Everything()) + if err != nil { + s.error(w, r, err) + return + } + + sort.Sort(namespacesByName(namespaces)) + + resp := namespacesresponse{ + APIResponse: APIResponse{ + Success: true, + }, + Namespaces: make([]string, 0), + } + + for _, namespace := range namespaces { + resp.Namespaces = append(resp.Namespaces, namespace.Name) + } + + s.respond(w, r, resp) +} diff --git a/sort.go b/sort.go index 5f6fdd20..ab070744 100644 --- a/sort.go +++ b/sort.go @@ -49,3 +49,18 @@ func (pvcs persistentVolumeClaimsByName) Less(a, b int) bool { func (pvcs persistentVolumeClaimsByName) Swap(a, b int) { pvcs[a], pvcs[b] = pvcs[b], pvcs[a] } + +// Namespaces by Name +type namespacesByName []*corev1.Namespace + +func (namespaces namespacesByName) Len() int { + return len(namespaces) +} + +func (namespaces namespacesByName) Less(a, b int) bool { + return namespaces[a].Name < namespaces[b].Name +} + +func (namespaces namespacesByName) Swap(a, b int) { + namespaces[a], namespaces[b] = namespaces[b], namespaces[a] +}