From 72b4b094bd8c9edc29bbbcc2b20049b6546497d4 Mon Sep 17 00:00:00 2001 From: "lukasz.widera@vshn.ch" Date: Thu, 10 Oct 2024 12:52:56 +0200 Subject: [PATCH] minimal collabora on Openshift and Kind Signed-off-by: Nicolas Bigler --- apis/vshn/v1/vshn_nextcloud.go | 7 +- apis/vshn/v1/zz_generated.deepcopy.go | 1 - crds/vshn.appcat.vshn.io_vshnnextclouds.yaml | 33 +- crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml | 39 +- .../functions/vshnnextcloud/billing.go | 5 +- .../functions/vshnnextcloud/collabora.go | 531 +++++++++++++- .../functions/vshnnextcloud/files/coolkit.xml | 691 ++++++++++++++++++ .../functions/vshnnextcloud/files/coolwsd.xml | 350 +++++++++ .../vshnnextcloud/files/install-collabora.sh | 37 + .../vshnnextcloud/files/sample-key.rsa | 27 + 10 files changed, 1626 insertions(+), 95 deletions(-) create mode 100644 pkg/comp-functions/functions/vshnnextcloud/files/coolkit.xml create mode 100644 pkg/comp-functions/functions/vshnnextcloud/files/coolwsd.xml create mode 100644 pkg/comp-functions/functions/vshnnextcloud/files/install-collabora.sh create mode 100644 pkg/comp-functions/functions/vshnnextcloud/files/sample-key.rsa diff --git a/apis/vshn/v1/vshn_nextcloud.go b/apis/vshn/v1/vshn_nextcloud.go index 293df45f2b..572e621557 100644 --- a/apis/vshn/v1/vshn_nextcloud.go +++ b/apis/vshn/v1/vshn_nextcloud.go @@ -13,6 +13,7 @@ import ( //go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnnextclouds.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.service.properties.postgreSQLParameters.default={})" //go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnnextclouds.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.backup.default={})" //go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnnextclouds.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.security.default={})" +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnnextclouds.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.service.collabora.default={})" // +kubebuilder:object:root=true @@ -172,8 +173,10 @@ func (v *VSHNNextcloud) SetInstanceNamespaceStatus() { } type CollaboraSpec struct { - FQDN string `json:"fqdn,omitempty"` - Size VSHNSizeSpec `json:"size,omitempty"` + //+kubebuilder:default=false + Enabled bool `json:"enabled"` + //+kubebuilder:validation:Required + FQDN string `json:"fqdn,omitempty"` } // +kubebuilder:object:generate=true diff --git a/apis/vshn/v1/zz_generated.deepcopy.go b/apis/vshn/v1/zz_generated.deepcopy.go index c7cb758b00..eb06466284 100644 --- a/apis/vshn/v1/zz_generated.deepcopy.go +++ b/apis/vshn/v1/zz_generated.deepcopy.go @@ -14,7 +14,6 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CollaboraSpec) DeepCopyInto(out *CollaboraSpec) { *out = *in - out.Size = in.Size } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollaboraSpec. diff --git a/crds/vshn.appcat.vshn.io_vshnnextclouds.yaml b/crds/vshn.appcat.vshn.io_vshnnextclouds.yaml index 8b8d87ba3d..3542db643b 100644 --- a/crds/vshn.appcat.vshn.io_vshnnextclouds.yaml +++ b/crds/vshn.appcat.vshn.io_vshnnextclouds.yaml @@ -4853,34 +4853,13 @@ spec: properties: collabora: properties: + enabled: + default: false + type: boolean fqdn: type: string - size: - description: VSHNSizeSpec contains settings to control the sizing of a service. - properties: - cpu: - description: CPU defines the amount of Kubernetes CPUs for an instance. - type: string - disk: - description: Disk defines the amount of disk space for an instance. - type: string - memory: - description: Memory defines the amount of memory in units of bytes for an instance. - type: string - plan: - description: Plan is the name of the resource plan that defines the compute resources. - type: string - requests: - description: Requests defines CPU and memory requests for an instance - properties: - cpu: - description: CPU defines the amount of Kubernetes CPUs for an instance. - type: string - memory: - description: Memory defines the amount of memory in units of bytes for an instance. - type: string - type: object - type: object + required: + - enabled type: object fqdn: description: |- @@ -9953,6 +9932,8 @@ spec: required: - fqdn type: object + collabora: + default: {} size: description: Size contains settings to control the sizing of a service. properties: diff --git a/crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml b/crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml index a9ac8efbbb..397a403d59 100644 --- a/crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml +++ b/crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml @@ -5577,42 +5577,13 @@ spec: properties: collabora: properties: + enabled: + default: false + type: boolean fqdn: type: string - size: - description: VSHNSizeSpec contains settings to control - the sizing of a service. - properties: - cpu: - description: CPU defines the amount of Kubernetes - CPUs for an instance. - type: string - disk: - description: Disk defines the amount of disk space - for an instance. - type: string - memory: - description: Memory defines the amount of memory in - units of bytes for an instance. - type: string - plan: - description: Plan is the name of the resource plan - that defines the compute resources. - type: string - requests: - description: Requests defines CPU and memory requests - for an instance - properties: - cpu: - description: CPU defines the amount of Kubernetes - CPUs for an instance. - type: string - memory: - description: Memory defines the amount of memory - in units of bytes for an instance. - type: string - type: object - type: object + required: + - enabled type: object fqdn: description: |- diff --git a/pkg/comp-functions/functions/vshnnextcloud/billing.go b/pkg/comp-functions/functions/vshnnextcloud/billing.go index 0fe1d72495..e6c5463cad 100644 --- a/pkg/comp-functions/functions/vshnnextcloud/billing.go +++ b/pkg/comp-functions/functions/vshnnextcloud/billing.go @@ -3,9 +3,9 @@ package vshnnextcloud import ( "context" "fmt" + xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" v1 "github.com/vshn/appcat/v4/apis/vshn/v1" - "github.com/vshn/appcat/v4/pkg/comp-functions/functions/common" "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" ) @@ -16,5 +16,6 @@ func AddServiceBillingLabel(ctx context.Context, comp *v1.VSHNNextcloud, svc *ru return runtime.NewFatalResult(fmt.Errorf("can't get composite: %w", err)) } - return common.InjectBillingLabelToService(ctx, svc, comp) + //return common.InjectBillingLabelToService(ctx, svc, comp) + return runtime.NewNormalResult("Temporarly disabled") } diff --git a/pkg/comp-functions/functions/vshnnextcloud/collabora.go b/pkg/comp-functions/functions/vshnnextcloud/collabora.go index 47131ec060..a32fcc1ef3 100644 --- a/pkg/comp-functions/functions/vshnnextcloud/collabora.go +++ b/pkg/comp-functions/functions/vshnnextcloud/collabora.go @@ -2,74 +2,184 @@ package vshnnextcloud import ( "context" + "crypto/md5" + _ "embed" "fmt" valid "github.com/asaskevich/govalidator" + cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" + "gopkg.in/yaml.v2" v1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/ptr" ) +//go:embed files/coolwsd.xml +var coolwsd string + +//go:embed files/coolkit.xml +var coolkit string + +//go:embed files/sample-key.rsa +var sampleKey string + +//go:embed files/install-collabora.sh +var installCollabora string + func DeployCollabora(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) *xfnproto.Result { err := svc.GetObservedComposite(comp) if err != nil { - return runtime.NewFatalResult(fmt.Errorf("cannot get composite: %w", err)) + return runtime.NewWarningResult(err.Error()) + } + + if !comp.Spec.Parameters.Service.Collabora.Enabled { + return runtime.NewNormalResult("Collabora not enabled") } if !valid.IsDNSName(comp.Spec.Parameters.Service.Collabora.FQDN) { - return runtime.NewFatalResult(fmt.Errorf("invalid FQDN")) + return runtime.NewWarningResult("Collabora FQDN is not a valid DNS name") + } + + // Create service account + err = createServiceAccount(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + // Create security context role + err = createSecurityContextRole(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + // Create security context role binding + err = createSecurityContextRoleBinding(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + // Create coolwsd config map + err = createCoolWSDConfigMap(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + // Create issuer + err = createIssuer(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + // Create certificate + err = createCertificate(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + // Create watch-only object + err = createWatchOnlyObject(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + // copy from watched certificate to new one + err = createSecretForOpenshiftRoute(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + releaseExists := svc.WaitForObservedDependencies(comp.GetName(), comp.GetName()+"-release") + if !releaseExists { + return runtime.NewWarningResult("Nextcloud instance not yet ready") } // Add Collabora deployment - err = AddCollaboraDeployment(ctx, comp, svc) + err = AddCollaboraSts(comp, svc) if err != nil { - return runtime.NewFatalResult(fmt.Errorf("cannot add Collabora deployment: %w", err)) + return runtime.NewWarningResult(err.Error()) } // Add Collabora service - err = AddCollaboraService(ctx, comp, svc) + err = AddCollaboraService(comp, svc) if err != nil { - return runtime.NewFatalResult(fmt.Errorf("cannot add Collabora service: %w", err)) + return runtime.NewWarningResult(err.Error()) } // Add Collabora ingress - err = AddCollaboraIngress(ctx, comp, svc) + err = AddCollaboraIngress(comp, svc) if err != nil { - return runtime.NewFatalResult(fmt.Errorf("cannot add Collabora ingress: %w", err)) + return runtime.NewWarningResult(err.Error()) } - return runtime.NewNormalResult("Collabora deployed") + // Create install Collabora config map + err = createInstallCollaboraConfigMap(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + // Create install Collabora job + err = createInstallCollaboraJob(comp, svc) + if err != nil { + return runtime.NewWarningResult(err.Error()) + } + + return runtime.NewNormalResult("Collabora deployed and configured!") } -func AddCollaboraDeployment(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { +func AddCollaboraSts(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { - deployment := &v1.Deployment{ + sts := &v1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: comp.GetName() + "-collabora-code", - Namespace: comp.GetNamespace(), - Labels: map[string]string{"app": comp.GetName() + "-collabora-code"}, + Namespace: comp.GetInstanceNamespace(), + Labels: map[string]string{ + "app": comp.GetName() + "-collabora-code", + }, }, - Spec: v1.DeploymentSpec{ + Spec: v1.StatefulSetSpec{ Replicas: ptr.To[int32](1), Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"app": comp.GetName() + "-collabora-code"}, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Name: comp.GetName() + "-collabora-code", + Name: comp.GetName() + "-collabora-code", + Labels: map[string]string{"app": comp.GetName() + "-collabora-code"}, }, Spec: corev1.PodSpec{ + ServiceAccountName: comp.GetName() + "-collabora-code-sa", + // SecurityContext: &corev1.PodSecurityContext{ + // // https://github.com/CollaboraOnline/online/blob/master/docker/from-packages/Dockerfile#L138 + // RunAsUser: ptr.To[int64](100), + // }, Containers: []corev1.Container{ { - Name: comp.GetName() + "-collabora-code", - Image: "collabora/code", + Env: []corev1.EnvVar{ + { + Name: "DONT_GEN_SSL_CERT", + Value: "true", + }, + { + // https://github.com/nextcloud/all-in-one/discussions/2314 + // https://github.com/CollaboraOnline/online/issues/3102 + Name: "extra_params", + Value: "--o:net.proto=IPv4", + }, + }, + Image: "collabora/code:24.04.8.1.1", + //Image: "collabora/code:latest", + Name: comp.GetName() + "-collabora-code", Ports: []corev1.ContainerPort{ { ContainerPort: 9980, @@ -77,12 +187,104 @@ func AddCollaboraDeployment(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc }, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("250m"), - corev1.ResourceMemory: resource.MustParse("1000Mi"), + corev1.ResourceCPU: resource.MustParse("900m"), + corev1.ResourceMemory: resource.MustParse("1200Mi"), }, Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("100m"), - corev1.ResourceMemory: resource.MustParse("400Mi"), + corev1.ResourceCPU: resource.MustParse("900m"), + corev1.ResourceMemory: resource.MustParse("1200Mi"), + }, + }, + + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + // https://help.nextcloud.com/t/nextcloud-office-could-not-establish-connection-to-the-collabora-online-server/185721/8 + "FOWNER", + "SYS_CHROOT", + "CHOWN", + // https://github.com/chrisingenhaag/helm/blob/main/charts/collabora-code/values.yaml#L64 + "MKNOD", + }, + }, + // https://github.com/CollaboraOnline/online/blob/master/docker/from-packages/Dockerfile#L138 + RunAsUser: ptr.To[int64](100), + }, + + // Mount certificates + VolumeMounts: []corev1.VolumeMount{ + { + Name: "collabora-code-tls", + MountPath: "/etc/coolwsd/cert.pem", + SubPath: "tls.crt", + }, + { + Name: "collabora-code-tls", + MountPath: "/etc/coolwsd/key.pem", + SubPath: "tls.key", + }, + { + Name: "collabora-code-tls", + MountPath: "/etc/coolwsd/ca-chain.cert.pem", + SubPath: "ca.crt", + }, + { + Name: "coolwsd-config", + MountPath: "/etc/coolwsd/coolwsd.xml", + SubPath: "coolwsd.xml", + }, + { + // need to attach it, it's probably generated by coolwsd + Name: "coolwsd-config", + MountPath: "/etc/coolwsd/coolkitconfig.xcu", + SubPath: "coolkit.xml", + }, + { + // it reports an error if this file is missing + Name: "coolwsd-config", + MountPath: "/etc/coolwsd/proof_key", + SubPath: "sample-key.rsa", + }, + { + Name: "tmp", + MountPath: "/tmp", + }, + { + // it creates chroots there + Name: "child-roots", + MountPath: "/opt/cool/child-roots/", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "tmp", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "child-roots", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "collabora-code-tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "collabora-code-tls", + }, + }, + }, + { + Name: "coolwsd-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "collabora-code-coolwsd-config", + }, }, }, }, @@ -92,21 +294,22 @@ func AddCollaboraDeployment(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc }, } - return svc.SetDesiredKubeObject(deployment, comp.GetName()+"-collabora-code-deployment") + return svc.SetDesiredKubeObject(sts, comp.GetName()+"-collabora-code-deployment") } -func AddCollaboraService(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { +func AddCollaboraService(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: comp.GetName() + "-collabora-code", - Namespace: comp.GetNamespace(), + Namespace: comp.GetInstanceNamespace(), Labels: map[string]string{"app": comp.GetName() + "-collabora-code"}, }, Spec: corev1.ServiceSpec{ Selector: map[string]string{"app": comp.GetName() + "-collabora-code"}, Ports: []corev1.ServicePort{ { - Port: 9980, + Port: 9980, + TargetPort: intstr.FromInt(9980), }, }, }, @@ -115,16 +318,18 @@ func AddCollaboraService(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *r return svc.SetDesiredKubeObject(service, comp.GetName()+"-collabora-code-service") } -func AddCollaboraIngress(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { +func AddCollaboraIngress(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { ingress := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: comp.GetName() + "-collabora-code", - Namespace: comp.GetNamespace(), + Namespace: comp.GetInstanceNamespace(), Labels: map[string]string{"app": comp.GetName() + "-collabora-code"}, Annotations: map[string]string{ - "cert-manager.io/cluster-issuer": "letsencrypt-staging", - "nginx.ingress.kubernetes.io/proxy-body-size": "0", + // this is the certificate we will use for the ingress + "cert-manager.io/cluster-issuer": "letsencrypt-staging", + "route.openshift.io/termination": "reencrypt", + "route.openshift.io/destination-ca-certificate-secret": comp.GetName() + "-collabora-code-ingress-certificate", }, }, Spec: networkingv1.IngressSpec{ @@ -154,12 +359,278 @@ func AddCollaboraIngress(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *r TLS: []networkingv1.IngressTLS{ { Hosts: []string{comp.Spec.Parameters.Service.Collabora.FQDN}, - SecretName: comp.GetName() + "-collabora-code-ingress-cert", + SecretName: comp.GetName() + "-collabora-code-tls", }, }, }, } + if svc.Config.Data["ingress_annotations"] != "" { + annotations := map[string]string{} + + err := yaml.Unmarshal([]byte(svc.Config.Data["ingress_annotations"]), annotations) + if err != nil { + return fmt.Errorf("cannot unmarshal ingress annotations from input: %w", err) + } + + if val, ok := annotations["cert-manager.io/cluster-issuer"]; ok { + ingress.Annotations["cert-manager.io/cluster-issuer"] = val + } + } + return svc.SetDesiredKubeObject(ingress, comp.GetName()+"-collabora-code-ingress") +} + +func createIssuer(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + issuer := &cmv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName() + "-collabora-code-issuer", + Namespace: comp.GetInstanceNamespace(), + }, + Spec: cmv1.IssuerSpec{ + IssuerConfig: cmv1.IssuerConfig{ + SelfSigned: &cmv1.SelfSignedIssuer{}, + }, + }, + } + + return svc.SetDesiredKubeObject(issuer, comp.GetName()+"-collabora-code-issuer") +} + +func createCertificate(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + certificate := &cmv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName() + "-collabora-code-certificate", + Namespace: comp.GetInstanceNamespace(), + }, + Spec: cmv1.CertificateSpec{ + SecretName: "collabora-code-tls", + DNSNames: []string{comp.Spec.Parameters.Service.Collabora.FQDN}, + IssuerRef: certmgrv1.ObjectReference{ + Name: comp.GetName() + "-collabora-code-issuer", + }, + }, + } + + return svc.SetDesiredKubeObject(certificate, comp.GetName()+"-collabora-code-certificate") +} + +func createCoolWSDConfigMap(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "collabora-code-coolwsd-config", + Namespace: comp.GetInstanceNamespace(), + }, + Data: map[string]string{ + "coolwsd.xml": coolwsd, + "coolkit.xml": coolkit, + "sample-key.rsa": sampleKey, + }, + } + + return svc.SetDesiredKubeObject(cm, comp.GetName()+"-collabora-code-coolwsd-config") +} + +func createServiceAccount(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName() + "-collabora-code-sa", + Namespace: comp.GetInstanceNamespace(), + Labels: map[string]string{"app": comp.GetName() + "-collabora-code"}, + }, + } + + return svc.SetDesiredKubeObject(sa, comp.GetName()+"-collabora-code-sa") +} + +func createSecurityContextRole(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName() + "-collabora-code-role", + Namespace: comp.GetInstanceNamespace(), + Labels: map[string]string{"app": comp.GetName() + "-collabora-code"}, + }, + Rules: []rbacv1.PolicyRule{ + { + // pods + APIGroups: []string{""}, + Resources: []string{"pods", "pods/log", "pods/status", "pods/attach", "pods/exec"}, + Verbs: []string{"*"}, + }, + { + // deployments + APIGroups: []string{"apps"}, + Resources: []string{"deployments"}, + Verbs: []string{"get", "delete", "watch", "list", "patch", "update", "create"}, + }, + }, + } + + if svc.Config.Data["isOpenshift"] == "true" { + role.Rules = append(role.Rules, rbacv1.PolicyRule{ + APIGroups: []string{"security.openshift.io"}, + Resources: []string{"securitycontextconstraints"}, + ResourceNames: []string{"appcat-collabora"}, + Verbs: []string{"use"}, + }) + } + + return svc.SetDesiredKubeObject(role, comp.GetName()+"-collabora-code-role") +} + +func createSecurityContextRoleBinding(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName() + "-collabora-code-role-binding", + Namespace: comp.GetInstanceNamespace(), + Labels: map[string]string{"app": comp.GetName() + "-collabora-code"}, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: comp.GetName() + "-collabora-code-sa", + Namespace: comp.GetInstanceNamespace(), + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "Role", + Name: comp.GetName() + "-collabora-code-role", + APIGroup: "rbac.authorization.k8s.io", + }, + } + + return svc.SetDesiredKubeObject(roleBinding, comp.GetName()+"-collabora-code-role-binding") +} + +func createWatchOnlyObject(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + // Create a watch-only object to trigger a re-deploy + wo := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "collabora-code-tls", + Namespace: comp.GetInstanceNamespace(), + }, + } + + return svc.SetDesiredKubeObserveObject(wo, comp.GetName()+"-watch-only-secret") +} + +func createSecretForOpenshiftRoute(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + secretFromWatchOnly := &corev1.Secret{} + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName() + "-collabora-code-ingress-certificate", + Namespace: comp.GetInstanceNamespace(), + Labels: map[string]string{"app": comp.GetName() + "-collabora-code"}, + }, + Data: map[string][]byte{}, + } + + err := svc.GetObservedKubeObject(secretFromWatchOnly, comp.GetName()+"-watch-only-secret") + if err != nil { + return err + } + + secret.Data["tls.crt"] = secretFromWatchOnly.Data["ca.crt"] + + return svc.SetDesiredKubeObject(secret, comp.GetName()+"collabora-code-ingress-certificate") +} + +func createInstallCollaboraConfigMap(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "collabora-install-script", + Namespace: comp.GetInstanceNamespace(), + Labels: map[string]string{"app": comp.GetName() + "-collabora-code"}, + }, + Data: map[string]string{ + "install-collabora.sh": installCollabora, + }, + } + + return svc.SetDesiredKubeObject(cm, comp.GetName()+"-collabora-install-script") +} + +func createInstallCollaboraJob(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { + + // get hash from config, se job will be triggered if script changes + + observedJob := &batchv1.Job{} + hash := md5.New() + + _, _ = hash.Write([]byte(installCollabora)) + checksum := hash.Sum(nil) + + strChecksum := fmt.Sprintf("%x", checksum) + err := svc.GetObservedKubeObject(observedJob, comp.GetName()+"-collabora-install-job") + if err == nil { + observedHash, ok := observedJob.Labels["script"] + if ok && (observedHash != strChecksum) { + // in this place I want to return no job and with next reconciliation I recreate it + // whole point is, that there are immutable fields everywhere in job and I cannot update it effectively + return nil + } + } + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName() + "-install-collabora", + Namespace: comp.GetInstanceNamespace(), + Labels: map[string]string{ + "app": comp.GetName() + "-collabora-code", + "script": fmt.Sprintf("%x", checksum), + }, + }, + Spec: batchv1.JobSpec{ + BackoffLimit: ptr.To[int32](100000), + + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName() + "-install-collabora", + Labels: map[string]string{ + "app": comp.GetName() + "-install-collabora", + }, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: comp.GetName() + "-collabora-code-sa", + Containers: []corev1.Container{ + { + Name: comp.GetName() + "-install-collabora", + Image: "quay.io/appuio/oc:v4.13", + Command: []string{ + "bash", + "-c", + fmt.Sprintf("/install-collabora.sh %s %s %s %s", svc.Config.Data["isOpenshift"], comp.GetInstanceNamespace(), comp.GetName(), comp.Spec.Parameters.Service.Collabora.FQDN), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "collabora-install-script", + MountPath: "/install-collabora.sh", + SubPath: "install-collabora.sh", + ReadOnly: false, + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "collabora-install-script", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + DefaultMode: ptr.To[int32](0755), + LocalObjectReference: corev1.LocalObjectReference{ + Name: "collabora-install-script", + }, + }, + }, + }, + }, + + RestartPolicy: corev1.RestartPolicyOnFailure, + }, + }, + }, + } + return svc.SetDesiredKubeObject(job, comp.GetName()+"-collabora-install-job") } diff --git a/pkg/comp-functions/functions/vshnnextcloud/files/coolkit.xml b/pkg/comp-functions/functions/vshnnextcloud/files/coolkit.xml new file mode 100644 index 0000000000..d6df6f1d54 --- /dev/null +++ b/pkg/comp-functions/functions/vshnnextcloud/files/coolkit.xml @@ -0,0 +1,691 @@ + + + + +false + + +11 + + +false + + +true + + +false + +true + + +file:///tmp + + +true + + +false +false +false + + +NeverMatchAnyUrlSuffix + + +Calibri;Carlito;Liberation Sans;Albany AMT;Albany;Arial;Noto Sans;Arimo;Nimbus Sans L;DejaVu Sans;Helvetica;Lucida;Geneva;Helmet;Arial;Noto Sans Unicode MS;Lucida Sans Unicode;Tahoma;SansSerif +Calibri;Carlito;Liberation Sans;Albany AMT;Albany;Arial;Noto Sans;Arimo;Nimbus Sans L;DejaVu Sans;Helvetica;Lucida;Geneva;Helmet;Arial;Noto Sans Unicode MS;Lucida Sans Unicode;Tahoma;SansSerif +Calibri;Carlito;Liberation Sans;Albany AMT;Albany;Arial;Noto Sans;Arimo;Nimbus Sans L;DejaVu Sans;Helvetica;Lucida;Geneva;Helmet;Arial;Noto Sans Unicode MS;Lucida Sans Unicode;Tahoma;SansSerif +Calibri;Carlito;Liberation Sans;Albany AMT;Albany;Arial;Noto Sans;Arimo;Nimbus Sans L;DejaVu Sans;Helvetica;Lucida;Geneva;Helmet;Arial;Noto Sans Unicode MS;Lucida Sans Unicode;Tahoma;SansSerif + +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker +org.openoffice.lingu.LanguageToolGrammarChecker + + +anydefaulthidden + + + + + + + + 16777215 + + + + + 12632256 + + + true + + + + + 14671838 + + + + + 12632256 + + + true + + + + + 12632256 + + + true + + + + + 128 + + + true + + + + + 204 + + + true + + + + + 16711680 + + + + + 255 + + + + + 16711935 + + + + + 8421504 + + + true + + + + + 12632256 + + + + + 12632256 + + + true + + + + + 12632256 + + + true + + + + + 0 + + + true + + + + + 32768 + + + + + 12632256 + + + true + + + + + 223651 + + + + + 128 + + + + + 255 + + + + + 65280 + + + + + 16711680 + + + + + 8421504 + + + + + 13421772 + + + + + 128 + + + + + 2293980 + + + + + 6710886 + + + + + 2293980 + + + false + + + + + 16711680 + + + true + + + + + 16711935 + + + + + 255 + + + + + 16711680 + + + + + 15667199 + + + + + 16777152 + + + + + 255 + + + + + 32768 + + + + + 0 + + + + + 12632256 + + + + + 6710886 + + + true + + + + + 16777215 + + + + + 32768 + + + + + 8421504 + + + + + 16711680 + + + + + 16711680 + + + + + 128 + + + + + 128 + + + + + 8388608 + + + + + 39168 + + + + + 0 + + + + + 13531904 + + + + + 0 + + + + + 230 + + + + + 2465181 + + + + + 8421504 + + + + + + + + + + 1842204 + + + + + 8421504 + + + true + + + + + 3355443 + + + + + 8421504 + + + true + + + + + 1842204 + + + true + + + + + 1939955 + + + true + + + + + 10181046 + + + true + + + + + 13181214 + + + + + 7512015 + + + + + 7865203 + + + + + 1842204 + + + true + + + + + 8421504 + + + + + 12632256 + + + true + + + + + 1842204 + + + true + + + + + 0 + + + true + + + + + 1993273 + + + + + 6710886 + + + true + + + + + 11847644 + + + + + 7512015 + + + + + 255 + + + + + 65280 + + + + + 16711680 + + + + + 8421504 + + + + + 6710886 + + + + + 128 + + + + + 2293980 + + + + + 6710886 + + + + + 2293980 + + + false + + + + + 16711680 + + + true + + + + + 16711935 + + + + + 3494505 + + + + + 13181214 + + + + + 861141 + + + + + 15245826 + + + + + 7512015 + + + + + 7847013 + + + + + 15658734 + + + + + 1842204 + + + + + 6710886 + + + true + + + + + 1842204 + + + + + 14543051 + + + + + 15658734 + + + + + 16754342 + + + + + 16754342 + + + + + 11847644 + + + + + 11847644 + + + + + 16726072 + + + + + 39168 + + + + + 0 + + + + + 13531904 + + + + + 0 + + + + + 230 + + + + + 2465181 + + + + + 8421504 + + + + + + \ No newline at end of file diff --git a/pkg/comp-functions/functions/vshnnextcloud/files/coolwsd.xml b/pkg/comp-functions/functions/vshnnextcloud/files/coolwsd.xml new file mode 100644 index 0000000000..737d42e95d --- /dev/null +++ b/pkg/comp-functions/functions/vshnnextcloud/files/coolwsd.xml @@ -0,0 +1,350 @@ + + + + + + + + + + false + + + de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru + + + + + + + + false + + + + + false + + + + + + + + + + + + + true + + + 4 + + + + 4 + 5 + 5 + false + 96 + 3600 + 30 + 300 + true + true + false + 0 + 8000 + 0 + 0 + 100 + 5 + 100 + 500 + 5000 + + 10000 + 60 + 300 + 3072 + 85 + 120 + + + + + 300 + 900 + + + + + + + true + + warning + trace + Socket,WebSocket,Admin,Pixel + notice + fatal + false + + -INFO-WARN + + + /var/log/coolwsd.log + never + timestamp + true + 10 days + 10 + true + false + + + false + 82589933 + + false + false + false + + + + + /var/log/coolwsd.trace.json + + + false + + + + + + + + false + + + + + + all + any + + + + 192\.168\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:192\.168\.[0-9]{1,3}\.[0-9]{1,3} + 127\.0\.0\.1 + ::ffff:127\.0\.0\.1 + ::1 + 172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3} + 172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3} + 172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3} + 10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} + + + 192\.168\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:192\.168\.[0-9]{1,3}\.[0-9]{1,3} + 127\.0\.0\.1 + ::ffff:127\.0\.0\.1 + ::1 + 172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3} + 172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3} + 172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3} + 10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} + localhost + + + + + + + + + + + + true + + false + /etc/coolwsd/cert.pem + /etc/coolwsd/key.pem + /etc/coolwsd/ca-chain.cert.pem + false + + + 1000 + + + + + + + false + 31536000 + + + + + true + + + true + + 1800 + false + 1 + false + false + + + + + + + + + + + + + + default + true + + + + + + 0 + + 900 + + + + + + + + + + + + true + + + + + + + + + + true + false + + + + true + true + true + true + + + + + + + + + + + + + + + + + + + false + + + + + + + false + + + + log + + + + + + + false + + + + + + + + + + + true + + + https://help.collaboraoffice.com/help.html? + + + true + + + + + + + + diff --git a/pkg/comp-functions/functions/vshnnextcloud/files/install-collabora.sh b/pkg/comp-functions/functions/vshnnextcloud/files/install-collabora.sh new file mode 100644 index 0000000000..706bea1e4d --- /dev/null +++ b/pkg/comp-functions/functions/vshnnextcloud/files/install-collabora.sh @@ -0,0 +1,37 @@ + /bin/bash -x + +IS_OPENSHIFT=$1 +NAMESPACE=$2 +NEXTCLOUD_INSTANCE=$3 +COLLABORA_FQDN=$4 +COLLABORA_URL="https://$COLLABORA_FQDN" + + +/usr/bin/kubectl wait --for=condition=available --timeout=600s -n "$NAMESPACE" "deployments/$NEXTCLOUD_INSTANCE" + +if [ "$IS_OPENSHIFT" == "true" ]; then + #check if collabora is already installed + /usr/bin/kubectl exec -n "$NAMESPACE" "deployments/$NEXTCLOUD_INSTANCE" -- /var/www/html/occ app:get richdocuments + RC=$? + if [ "$RC" == 0 ]; then + echo "Collabora already installed" + exit 0 + fi + set -e + /usr/bin/kubectl exec -n "$NAMESPACE" "deployments/$NEXTCLOUD_INSTANCE" -- /var/www/html/occ app:install richdocuments + /usr/bin/kubectl exec -n "$NAMESPACE" "deployments/$NEXTCLOUD_INSTANCE" -- /var/www/html/occ config:app:set --value "$COLLABORA_URL" richdocuments wopi_url + /usr/bin/kubectl exec -n "$NAMESPACE" "deployments/$NEXTCLOUD_INSTANCE" -- /var/www/html/occ app:enable richdocuments +else + /usr/bin/kubectl exec -n "$NAMESPACE" "deployments/$NEXTCLOUD_INSTANCE" -- /usr/bin/su -s /bin/sh www-data -c "/var/www/html/occ app:get richdocuments" + RC=$? + if [ "$RC" == 0 ]; then + echo "Collabora already installed" + exit 0 + fi + set -e + /usr/bin/kubectl exec -n "$NAMESPACE" "deployments/$NEXTCLOUD_INSTANCE" -- /usr/bin/su -s /bin/sh www-data -c "/var/www/html/occ app:install richdocuments" + /usr/bin/kubectl exec -n "$NAMESPACE" "deployments/$NEXTCLOUD_INSTANCE" -- /usr/bin/su -s /bin/sh www-data -c "/var/www/html/occ config:app:set --value $COLLABORA_URL richdocuments wopi_url" + /usr/bin/kubectl exec -n "$NAMESPACE" "deployments/$NEXTCLOUD_INSTANCE" -- /usr/bin/su -s /bin/sh www-data -c "/var/www/html/occ app:enable richdocuments" +fi + +echo "Collabora installed, wopi_url configured and app enabled!" \ No newline at end of file diff --git a/pkg/comp-functions/functions/vshnnextcloud/files/sample-key.rsa b/pkg/comp-functions/functions/vshnnextcloud/files/sample-key.rsa new file mode 100644 index 0000000000..943687501d --- /dev/null +++ b/pkg/comp-functions/functions/vshnnextcloud/files/sample-key.rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAvIImAilKC6vry7nRYi+1cmbafYd2OtMlaJNyw86Rwu4COv1B +OCg01zwiGnLqbC3WWcfxrbuwOowRTYxBKQ8GdaoqkrXrgiLKFfAHtxDWpmuZZ6/6 +AzqneaTDV3yF1953ID4y0kFg8GxGVmc+eeIcUKKjYjcG/3yY5llefzFxC4ABNlpK +t19lmKzHIdPye0A7QUvSiPv3onfa1d+oPB8fB4o+9wV2k2ZbGhoKGCUqGdCcWZa8 +SQf6UoqWjRfJTdfI9j5XwLQWtuZqV+GRe6fIfDWQPhhslZAAPUj9q8jMOSZx16gX +7ioq4nNLk++0lVpLSqr5rZUKlkQ8yLbheI3S3QIDAQABAoIBAQCX03spYnfwzpcr +jo3Ftwc9DPoNAH2JA2atw5Q7gLSFe4zwVemhgUCN2jDdfJCycHij9xYAB5r4jLei +7cPNffyF6HhBkaQNPFfnM1wDZdx8TU1Q5btHvoYEw19Rp0llhBgOMAperamV+p7s +GFuJb0O+7j8Ja6iHPaMoY/JoHzodN6l2xa2QDR71jmhZkzOGcg8qOPh9cCVgfqQh +pUbkg/XPaFRfO7ufqzuVmF/gu3oFes6037YVq6rpl+xWkwfavPtXqLJSI+FVDWww +QJys0jcJH5rgYHUH9Vlmf5mbuWgsV4VoVkLGSouvD2Z/AZnQvlDygdB0kyMrzzPf +wO9LvrYBAoGBAObG5RynntSAPR03x9lLcto0njNldj1fhSGxxdPp6EakdaIMmT8E +ruae6hvCY+NgjxlPPtHh9ozvFvBhJ4JJLdW+FzyOsGGOvzzhHsxJ4g7E4wZ/9Jvz +syoDOBR7cEut4rwh4In8qd2/KdZDuX+2pox7xmMNOd74SRAUkjaRJHCpAoGBANEc +lfu/2cqVYj/MHZKBVP7Trk6PRBCgW7FNVoxW0UubCeLLxT+SkDZ5sZKlZPVlHWhs +5vSenuNQWTyQ+dRqN8WlZJgT4DpZ+lG2y7WIJU8eCBnYPGNBC8cdd0OJfh5uPmyw +mfMmSBwgSM99zQRICuGRk3uXQnTMX76sSEpPGA0VAoGBAMGVsFU0KqD5HcrnosYd +ay+tmdNlViUvfl8AYAFeolC33lFcHiTlyc19GsbEbsQqmH9ToU+wLACXAvgLl3fL +6R32QkmzLDP56WOpOAuIIo/fCs3GAivb5rvkTWOpFE73g03rYQ5/GwL/O5oX34BJ +eDY6EfW+/3+RaMCLcQdDA31ZAoGAJWU/TS4Z/KiKUW5nO3P1hzzDxzjHq1GCFEsI +V0CPXT30nqJiO+Qx9gv9PYRmcZwLjkkrZEOZln0f+DzU8K+uGaJiQvALAWRl54cR +Fv2p7mQofra01esj+A/E7EcCTt7YOwe6AEmwS5dabgDJUSKxD3XBMjDyqYCC/s5A +f0l1u6ECgYEAt+QSB/6UVjXIRxhdTPsJ0eZEsZaNNNAhLnSVDaE0kvZKPU1vHgsv +4Gb0RvnjjGckI358NI8BkvHfVWUvziyaU1RxRBTnOLF1e/PbE36TNFbwtsJcBK7m +hCXViZUUVvc7lkir74ZqhRJ6aqQ1KD2fCrxo6KLh9ruYU1FS8Y8L5tc= +-----END RSA PRIVATE KEY----- \ No newline at end of file