notebooks/components/pvcviewer-controller/api/v1alpha1/pvcviewer_webhook.go

199 lines
5.9 KiB
Go

/*
Copyright 2023.
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 v1alpha1
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"reflect"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"k8s.io/apimachinery/pkg/util/yaml"
corev1 "k8s.io/api/core/v1"
)
const DefaultPodSpecPathEnvName = "DEFAULT_POD_SPEC_PATH"
// log is for logging in this package.
var pvcviewerlog = logf.Log.WithName("pvcviewer-resource")
func (r *PVCViewer) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}
//+kubebuilder:webhook:path=/mutate-kubeflow-org-v1alpha1-pvcviewer,mutating=true,failurePolicy=fail,sideEffects=None,groups=kubeflow.org,resources=pvcviewers,verbs=create;update,versions=v1alpha1,name=mpvcviewer.kb.io,admissionReviewVersions=v1
var _ webhook.Defaulter = &PVCViewer{}
// Loads a PodSpec from a filename
func loadPodSpecDefaultsFromFile(filename string) (corev1.PodSpec, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
pvcviewerlog.Error(err, "Failed to read podSpec defaults from file "+filename)
return corev1.PodSpec{}, err
}
var podSpec corev1.PodSpec
if err := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(data), 100).Decode(&podSpec); err != nil {
pvcviewerlog.Error(err, "Failed to unmarshal podSpec defaults file "+filename)
return corev1.PodSpec{}, err
}
return podSpec, nil
}
// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *PVCViewer) Default() {
pvcviewerlog.Info("default", "name", r.Name)
if reflect.DeepEqual(r.Spec.PodSpec, corev1.PodSpec{}) {
pvcviewerlog.Info("Inferring default podSpec", "name", r.Name)
// Load default podSpec from file if environment variable is set
var defaultPodSpec corev1.PodSpec
if defaultPodSpecPath := os.Getenv(DefaultPodSpecPathEnvName); defaultPodSpecPath != "" {
var err error
defaultPodSpec, err = loadPodSpecDefaultsFromFile(defaultPodSpecPath)
if err != nil {
// We can't throw an error here, so we return
// This lets the validating webhook catch the error
return
}
}
// Check if a default podSpec was loaded from file
// If so, set it as the default, otherwise set a predefined default
if !reflect.DeepEqual(defaultPodSpec, corev1.PodSpec{}) {
r.Spec.PodSpec = defaultPodSpec
} else {
r.Spec.PodSpec.Containers = []corev1.Container{
{
Name: "pvcviewer",
Image: "filebrowser/filebrowser:latest",
Ports: []corev1.ContainerPort{
{
ContainerPort: 8080,
Protocol: corev1.ProtocolTCP,
},
},
Env: []corev1.EnvVar{
{
Name: "FB_ADDRESS",
Value: "0.0.0.0",
},
{
Name: "FB_PORT",
Value: "8080",
},
{
Name: "FB_DATABASE",
Value: "/tmp/filebrowser.db",
},
{
Name: "FB_NOAUTH",
Value: "true",
},
{
Name: "FB_BASEURL",
Value: fmt.Sprintf("%s/%s/%s/", r.Spec.Networking.BasePrefix, r.Namespace, r.Name),
},
},
WorkingDir: "/data",
VolumeMounts: []corev1.VolumeMount{
{
Name: "viewer-volume",
MountPath: "/data",
},
},
},
}
}
// Always set the pod.spec.volume referred to by viewer.spec.pvc
// This way, the default file can be used without having to know the pvc name in advance
// Make sure to only append the volume so additional volumes may be defined
r.Spec.PodSpec.Volumes = append(r.Spec.PodSpec.Volumes, corev1.Volume{
Name: "viewer-volume",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: r.Spec.PVC,
},
},
})
}
}
//+kubebuilder:webhook:path=/validate-kubeflow-org-v1alpha1-pvcviewer,mutating=false,failurePolicy=fail,sideEffects=None,groups=kubeflow.org,resources=pvcviewers,verbs=create;update,versions=v1alpha1,name=vpvcviewer.kb.io,admissionReviewVersions=v1
var _ webhook.Validator = &PVCViewer{}
func (r *PVCViewer) validate() error {
if len(r.Spec.PVC) == 0 {
return fmt.Errorf("PVC name must be specified")
}
// Should be set by defaulting webhook
// However, it it throws an error, this needs to be caught here
if reflect.DeepEqual(r.Spec.PodSpec, corev1.PodSpec{}) {
return fmt.Errorf("PodSpec must be specified")
}
// Require the viewer.spec.PVC to be used in the podSpec.volumes
found := false
for _, volume := range r.Spec.PodSpec.Volumes {
if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == r.Spec.PVC {
found = true
break
}
}
if !found {
return fmt.Errorf("PVC %s must be used in the podSpec", r.Spec.PVC)
}
return nil
}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *PVCViewer) ValidateCreate() error {
pvcviewerlog.Info("validate create", "name", r.Name)
return r.validate()
}
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *PVCViewer) ValidateUpdate(old runtime.Object) error {
pvcviewerlog.Info("validate update", "name", r.Name)
return r.validate()
}
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *PVCViewer) ValidateDelete() error {
pvcviewerlog.Info("validate delete", "name", r.Name)
// We have not registered our webhook to validate delete operations.
return nil
}