diff --git a/api/apps/v1alpha1/nextcloud_types.go b/api/apps/v1alpha1/nextcloud_types.go index b9ded2a..9306ccb 100644 --- a/api/apps/v1alpha1/nextcloud_types.go +++ b/api/apps/v1alpha1/nextcloud_types.go @@ -45,6 +45,9 @@ type NextcloudSpec struct { //+kubebuilder:validation:Optional DisableSSO bool `json:"disableSSO"` + + //+kubebuilder:validation:Optional + EnablePreviews bool `json:"enablePreviews,omitempty"` } // NextcloudStatus defines the observed state of Nextcloud diff --git a/config/crd/bases/apps.libre.sh_nextclouds.yaml b/config/crd/bases/apps.libre.sh_nextclouds.yaml index 8495852..ced5d3e 100644 --- a/config/crd/bases/apps.libre.sh_nextclouds.yaml +++ b/config/crd/bases/apps.libre.sh_nextclouds.yaml @@ -57,6 +57,8 @@ spec: properties: disableSSO: type: boolean + enablePreviews: + type: boolean envFrom: items: description: EnvFromSource represents the source of a set of ConfigMaps diff --git a/internal/controller/apps/nextcloud_controller.go b/internal/controller/apps/nextcloud_controller.go index 2b84945..a43438a 100644 --- a/internal/controller/apps/nextcloud_controller.go +++ b/internal/controller/apps/nextcloud_controller.go @@ -145,6 +145,12 @@ func (r *NextcloudReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } + if nextcloud.Spec.EnablePreviews { + if err := r.reconcilePreviews(ctx, &nextcloud, &resources); err != nil { + return ctrl.Result{}, err + } + } + err = lshr.OnInstall(&nextcloud, func() error { log.Info("Installing") job, err := r.reconcileInstallJob(ctx, &nextcloud, &resources) @@ -179,16 +185,6 @@ func (r *NextcloudReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // err = r.reconcilePodDisruptionBudget(ctx, &nextcloud) - // if err != nil { - // return ctrl.Result{}, err - // } - - // _, err = r.reconcileHpa(ctx, &nextcloud, dep) - // if err != nil { - // return ctrl.Result{}, err - // } - _, err = r.reconcileService(ctx, &nextcloud) if err != nil { return ctrl.Result{}, err diff --git a/internal/controller/apps/nextcloud_controller_env.go b/internal/controller/apps/nextcloud_controller_env.go index 6d5b2bf..414725f 100644 --- a/internal/controller/apps/nextcloud_controller_env.go +++ b/internal/controller/apps/nextcloud_controller_env.go @@ -24,6 +24,7 @@ import ( appsv1alpha1 "libre.sh/api/apps/v1alpha1" lshcore "libre.sh/api/core/v1alpha1" lshmeta "libre.sh/api/meta/v1alpha1" + lshr "libre.sh/pkg/controller-runtime" ) func (r *NextcloudReconciler) getEnvVars(nextcloud *appsv1alpha1.Nextcloud, resources *nextcloudResources) []corev1.EnvVar { @@ -388,6 +389,37 @@ func (r *NextcloudReconciler) getEnvVars(nextcloud *appsv1alpha1.Nextcloud, reso }, } envs = append(envs, ssoEnvs...) + + if nextcloud.Spec.EnablePreviews { + envs = append(envs, []corev1.EnvVar{ + { + Name: "PREVIEWS_ENABLED", + Value: "true", + }, + { + Name: "PREVIEWS_URL", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lshr.GetResourceName(nextcloud, "previews"), + }, + Key: lshmeta.SecretURLKey, + }, + }, + }, + { + Name: "PREVIEWS_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lshr.GetResourceName(nextcloud, "previews"), + }, + Key: lshmeta.SecretPasswordKey, + }, + }, + }, + }...) + } } return envs diff --git a/internal/controller/apps/nextcloud_controller_net.go b/internal/controller/apps/nextcloud_controller_net.go index 3c9d6c3..0696417 100644 --- a/internal/controller/apps/nextcloud_controller_net.go +++ b/internal/controller/apps/nextcloud_controller_net.go @@ -33,7 +33,9 @@ func (r *NextcloudReconciler) reconcileService(ctx context.Context, nextcloud *l var service corev1.Service lshr.SetResourceNamespacedName(nextcloud, &service) err := lshr.CreateOrPatch(ctx, r, &service, func() error { - lshr.ApplyLabels(nextcloud, &service, nil) + lshr.ApplyLabels(nextcloud, &service, &lshr.LabelOpts{ + Component: "app", + }) service.Spec.Selector = lshr.ExtractLabelSelector(&service) service.Spec.Ports = []corev1.ServicePort{ { diff --git a/internal/controller/apps/nextcloud_controller_previews.go b/internal/controller/apps/nextcloud_controller_previews.go new file mode 100644 index 0000000..6835e30 --- /dev/null +++ b/internal/controller/apps/nextcloud_controller_previews.go @@ -0,0 +1,169 @@ +/* +Copyright 2024 IndieHosters. + +Licensed under the EUPL, Version 1.2 or later (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + +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 apps + +import ( + "context" + "fmt" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + appsv1alpha1 "libre.sh/api/apps/v1alpha1" + lshapps "libre.sh/api/apps/v1alpha1" + lshmeta "libre.sh/api/meta/v1alpha1" + lshr "libre.sh/pkg/controller-runtime" +) + +func (r *NextcloudReconciler) reconcilePreviews(ctx context.Context, nextcloud *appsv1alpha1.Nextcloud, resources *nextcloudResources) error { + + if err := r.reconcilePreviewsSecret(ctx, nextcloud, resources); err != nil { + return err + } + + if err := r.reconcilePreviewsService(ctx, nextcloud); err != nil { + return err + } + + return r.reconcilePreviewsDeployment(ctx, nextcloud, resources) +} + +func (r *NextcloudReconciler) reconcilePreviewsDeployment(ctx context.Context, nextcloud *appsv1alpha1.Nextcloud, resources *nextcloudResources) error { + var deployment appsv1.Deployment + lshr.SetResourceNamespacedName(nextcloud, &deployment, "previews") + return lshr.CreateOrPatch(ctx, r, &deployment, func() error { + lshr.ApplyLabels(nextcloud, &deployment, &lshr.LabelOpts{ + Component: "previews", + Version: lshr.GetVersion(nextcloud), + }) + deployment.Spec.Selector = &metav1.LabelSelector{ + MatchLabels: lshr.ExtractLabelSelector(&deployment), + } + deployment.Spec.Template.ObjectMeta.Labels = deployment.Labels + deployment.Spec.Template.Spec.TopologySpreadConstraints = []corev1.TopologySpreadConstraint{ + { + MaxSkew: 1, + TopologyKey: "kubernetes.io/hostname", + WhenUnsatisfiable: corev1.ScheduleAnyway, + LabelSelector: deployment.Spec.Selector, + }, + } + + if deployment.Spec.Replicas == nil { + replicas := int32(1) + deployment.Spec.Replicas = &replicas + } + + /* probeHandler := corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/health", + Port: intstr.FromInt(8088), + }, + } */ + + deployment.Spec.Template.Spec.Containers = []corev1.Container{ + { + Image: "registry.libre.sh/imaginary:6cd9edd1d3fb151eb773c14552886e4fc8e50138", + ImagePullPolicy: corev1.PullAlways, + Name: "app", + Ports: []corev1.ContainerPort{ + { + ContainerPort: 8088, + Name: "http", + Protocol: corev1.ProtocolTCP, + }, + }, + Args: []string{"-concurrency", "20", "-enable-url-source"}, + Env: []corev1.EnvVar{ + { + Name: "PORT", + Value: "8088", + }, + { + Name: "IMAGINARY_SECRET", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lshr.GetResourceName(nextcloud, "previews"), + }, + Key: lshmeta.SecretPasswordKey, + }, + }, + }, + }, + /* ReadinessProbe: &corev1.Probe{ + ProbeHandler: probeHandler, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: probeHandler, + }, + */ + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("300Mi"), + }, + }, + }, + } + + return controllerutil.SetControllerReference(nextcloud, &deployment, r.Scheme()) + }) +} + +func (r *NextcloudReconciler) reconcilePreviewsSecret(ctx context.Context, nextcloud *appsv1alpha1.Nextcloud, resources *nextcloudResources) error { + var secret corev1.Secret + lshr.SetResourceNamespacedName(nextcloud, &secret, "previews") + + secret.Namespace = nextcloud.Namespace + return lshr.CreateOrPatch(ctx, r, &secret, func() error { + if secret.Data == nil { + secret.Data = make(map[string][]byte) + } + if len(secret.Data[lshmeta.SecretPasswordKey]) == 0 { + secret.Data[lshmeta.SecretPasswordKey] = []byte(rand.String(32)) + } + secret.Data[lshmeta.SecretURLKey] = []byte(fmt.Sprintf("http://%s:8088", lshr.GetResourceName(nextcloud, "previews"))) + return controllerutil.SetControllerReference(nextcloud, &secret, r.Scheme()) + }) +} + +func (r *NextcloudReconciler) reconcilePreviewsService(ctx context.Context, nextcloud *lshapps.Nextcloud) error { + var service corev1.Service + lshr.SetResourceNamespacedName(nextcloud, &service, "previews") + return lshr.CreateOrPatch(ctx, r, &service, func() error { + lshr.ApplyLabels(nextcloud, &service, &lshr.LabelOpts{ + Component: "previews", + }) + service.Spec.Selector = lshr.ExtractLabelSelector(&service) + service.Spec.Ports = []corev1.ServicePort{ + { + Name: "http", + TargetPort: intstr.FromString("http"), + Port: 8088, + }, + } + return controllerutil.SetControllerReference(nextcloud, &service, r.Scheme()) + }) +}