notebooks/workspaces/backend/internal/models/workspacekinds/funcs.go

178 lines
6.2 KiB
Go

/*
Copyright 2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package workspacekinds
import (
"fmt"
kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1"
"k8s.io/utils/ptr"
)
// NewWorkspaceKindModelFromWorkspaceKind creates a WorkspaceKind model from a WorkspaceKind object.
func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKind) WorkspaceKind {
podLabels := make(map[string]string)
podAnnotations := make(map[string]string)
if wsk.Spec.PodTemplate.PodMetadata != nil {
// NOTE: we copy the maps to avoid creating a reference to the original maps.
for k, v := range wsk.Spec.PodTemplate.PodMetadata.Labels {
podLabels[k] = v
}
for k, v := range wsk.Spec.PodTemplate.PodMetadata.Annotations {
podAnnotations[k] = v
}
}
statusImageConfigMap := buildOptionMetricsMap(wsk.Status.PodTemplateOptions.ImageConfig)
statusPodConfigMap := buildOptionMetricsMap(wsk.Status.PodTemplateOptions.PodConfig)
// TODO: icons can either be a remote URL or read from a ConfigMap.
// in BOTH cases, we should cache and serve the image under a path on the backend API:
// /api/v1/workspacekinds/{name}/assets/icon
iconRef := ImageRef{
URL: fmt.Sprintf("/workspaces/backend/api/v1/workspacekinds/%s/assets/icon", wsk.Name),
}
// TODO: logos can either be a remote URL or read from a ConfigMap.
// in BOTH cases, we should cache and serve the image under a path on the backend API:
// /api/v1/workspacekinds/{name}/assets/logo
logoRef := ImageRef{
URL: fmt.Sprintf("/workspaces/backend/api/v1/workspacekinds/%s/assets/logo", wsk.Name),
}
return WorkspaceKind{
Name: wsk.Name,
DisplayName: wsk.Spec.Spawner.DisplayName,
Description: wsk.Spec.Spawner.Description,
Deprecated: ptr.Deref(wsk.Spec.Spawner.Deprecated, false),
DeprecationMessage: ptr.Deref(wsk.Spec.Spawner.DeprecationMessage, ""),
Hidden: ptr.Deref(wsk.Spec.Spawner.Hidden, false),
Icon: iconRef,
Logo: logoRef,
// TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context
ClusterMetrics: clusterMetrics{
Workspaces: wsk.Status.Workspaces,
},
PodTemplate: PodTemplate{
PodMetadata: PodMetadata{
Labels: podLabels,
Annotations: podAnnotations,
},
VolumeMounts: PodVolumeMounts{
Home: wsk.Spec.PodTemplate.VolumeMounts.Home,
},
Options: PodTemplateOptions{
ImageConfig: ImageConfig{
Default: wsk.Spec.PodTemplate.Options.ImageConfig.Spawner.Default,
Values: buildImageConfigValues(wsk.Spec.PodTemplate.Options.ImageConfig, statusImageConfigMap),
},
PodConfig: PodConfig{
Default: wsk.Spec.PodTemplate.Options.PodConfig.Spawner.Default,
Values: buildPodConfigValues(wsk.Spec.PodTemplate.Options.PodConfig, statusPodConfigMap),
},
},
},
}
}
func buildOptionMetricsMap(metrics []kubefloworgv1beta1.OptionMetric) map[string]int32 {
resultMap := make(map[string]int32)
for _, metric := range metrics {
resultMap[metric.Id] = metric.Workspaces
}
return resultMap
}
func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig, statusImageConfigMap map[string]int32) []ImageConfigValue {
imageConfigValues := make([]ImageConfigValue, len(imageConfig.Values))
for i := range imageConfig.Values {
option := imageConfig.Values[i]
imageConfigValues[i] = ImageConfigValue{
Id: option.Id,
DisplayName: option.Spawner.DisplayName,
Description: ptr.Deref(option.Spawner.Description, ""),
Labels: buildOptionLabels(option.Spawner.Labels),
Hidden: ptr.Deref(option.Spawner.Hidden, false),
Redirect: buildOptionRedirect(option.Redirect),
// TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context
ClusterMetrics: clusterMetrics{
Workspaces: statusImageConfigMap[option.Id],
},
}
}
return imageConfigValues
}
func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig, statusPodConfigMap map[string]int32) []PodConfigValue {
podConfigValues := make([]PodConfigValue, len(podConfig.Values))
for i := range podConfig.Values {
option := podConfig.Values[i]
podConfigValues[i] = PodConfigValue{
Id: option.Id,
DisplayName: option.Spawner.DisplayName,
Description: ptr.Deref(option.Spawner.Description, ""),
Labels: buildOptionLabels(option.Spawner.Labels),
Hidden: ptr.Deref(option.Spawner.Hidden, false),
Redirect: buildOptionRedirect(option.Redirect),
// TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context
ClusterMetrics: clusterMetrics{
Workspaces: statusPodConfigMap[option.Id],
},
}
}
return podConfigValues
}
func buildOptionLabels(labels []kubefloworgv1beta1.OptionSpawnerLabel) []OptionLabel {
optionLabels := make([]OptionLabel, len(labels))
for i := range labels {
optionLabels[i] = OptionLabel{
Key: labels[i].Key,
Value: labels[i].Value,
}
}
return optionLabels
}
func buildOptionRedirect(redirect *kubefloworgv1beta1.OptionRedirect) *OptionRedirect {
if redirect == nil {
return nil
}
var message *RedirectMessage
if redirect.Message != nil {
messageLevel := RedirectMessageLevelInfo
switch redirect.Message.Level {
case kubefloworgv1beta1.RedirectMessageLevelInfo:
messageLevel = RedirectMessageLevelInfo
case kubefloworgv1beta1.RedirectMessageLevelWarning:
messageLevel = RedirectMessageLevelWarning
case kubefloworgv1beta1.RedirectMessageLevelDanger:
messageLevel = RedirectMessageLevelDanger
}
message = &RedirectMessage{
Text: redirect.Message.Text,
Level: messageLevel,
}
}
return &OptionRedirect{
To: redirect.To,
Message: message,
}
}