From aa3dcab445286a179fe26bd09b3b3db5594e74bb Mon Sep 17 00:00:00 2001 From: Aatman Date: Mon, 5 Dec 2022 07:43:49 +0530 Subject: [PATCH] feat(ingress): aws alb (#294) * fix: only for v1beta1 * fix: make lb controller optional * fix: unit tests * fix: check if ingress is enabled or not weird interaction with GetIngressEnabled, it returns true by default this could be a problem when checking for other ingress options like aws ingress * fix: ingress class & ingress host check * feat: ingress class for alb * fix: annotation quotes * chore: fix style * chore: fix typo * feat: test check mattermost aws ingress * fix: address review comments * fix: use mattermost name for ingress class * fix: address comments * chore: test disable and enable lb * chore: address review comments * feat: get ports --- apis/mattermost/v1beta1/mattermost_types.go | 25 +++ apis/mattermost/v1beta1/mattermost_utils.go | 25 ++- .../v1beta1/zz_generated.deepcopy.go | 25 +++ .../v1beta1/zz_generated.openapi.go | 7 +- ...stallation.mattermost.com_mattermosts.yaml | 26 +++ config/rbac/role.yaml | 1 + .../mattermost/mattermost/mattermost.go | 37 +++- .../mattermost/mattermost/mattermost_test.go | 179 ++++++++++++++++++ pkg/mattermost/mattermost.go | 2 +- pkg/mattermost/mattermost_v1beta.go | 87 ++++++++- pkg/resources/create_resources.go | 32 ++++ 11 files changed, 438 insertions(+), 8 deletions(-) diff --git a/apis/mattermost/v1beta1/mattermost_types.go b/apis/mattermost/v1beta1/mattermost_types.go index 9e5ffa3f5..1e756b064 100644 --- a/apis/mattermost/v1beta1/mattermost_types.go +++ b/apis/mattermost/v1beta1/mattermost_types.go @@ -75,6 +75,9 @@ type MattermostSpec struct { // Ingress defines configuration for Ingress resource created by the Operator. // +optional Ingress *Ingress `json:"ingress,omitempty"` + + // +optional + AWSLoadBalancerController *AWSLoadBalancerController `json:"awsLoadBalancerController,omitempty"` // Volumes allows for mounting volumes from various sources into the // Mattermost application pods. // +optional @@ -177,6 +180,28 @@ type Ingress struct { IngressClass *string `json:"ingressClass,omitempty"` } +type AWSLoadBalancerController struct { + // An AWS ALB Ingress will be created instead of nginx + // +optional + Enabled bool `json:"enabled,omitempty"` + + // Certificate arn for the ALB, required if SSL enabled + // +optional + CertificateARN string `json:"certificateARN,omitempty"` + + // Whether the Ingress will be internetfacing, default is false + // +optional + InternetFacing bool `json:"internetFacing,omitempty"` + + // Hosts allows specifying additional domain names for Mattermost to use. + // +optional + Hosts []IngressHost `json:"hosts,omitempty"` + + // IngressClassName for your ingress + // +optional + IngressClassName string `json:"ingressClassName,omitempty"` +} + // IngressHost specifies additional hosts configuration. type IngressHost struct { HostName string `json:"hostName,omitempty"` diff --git a/apis/mattermost/v1beta1/mattermost_utils.go b/apis/mattermost/v1beta1/mattermost_utils.go index a91c37f28..48aba1556 100644 --- a/apis/mattermost/v1beta1/mattermost_utils.go +++ b/apis/mattermost/v1beta1/mattermost_utils.go @@ -47,7 +47,11 @@ const ( // SetDefaults set the missing values in the manifest to the default ones func (mm *Mattermost) SetDefaults() error { - if mm.IngressEnabled() && mm.GetIngressHost() == "" { + if mm.AWSLoadBalancerEnabled() && len(mm.Spec.AWSLoadBalancerController.Hosts) == 0 { + return errors.New("awsLoadBalancerController.hosts is required, but not set") + } + + if !mm.AWSLoadBalancerEnabled() && mm.IngressEnabled() && mm.GetIngressHost() == "" { return errors.New("ingress.host required, but not set") } if mm.Spec.Image == "" { @@ -74,6 +78,13 @@ func (mm *Mattermost) IngressEnabled() bool { return true } +func (mm *Mattermost) AWSLoadBalancerEnabled() bool { + if mm.Spec.AWSLoadBalancerController != nil { + return mm.Spec.AWSLoadBalancerController.Enabled + } + return false +} + // GetIngressHost returns Mattermost primary Ingress host. func (mm *Mattermost) GetIngressHost() string { if mm.Spec.Ingress == nil { @@ -113,6 +124,18 @@ func (mm *Mattermost) GetIngressHostNames() []string { return hosts } +func (mm *Mattermost) GetAWSLoadBalancerHostNames() []string { + hosts := []string{} + + if mm.Spec.AWSLoadBalancerController != nil { + for _, host := range mm.Spec.AWSLoadBalancerController.Hosts { + hosts = append(hosts, host.HostName) + } + } + + return hosts +} + // GetIngresAnnotations returns Mattermost Ingress annotations. func (mm *Mattermost) GetIngresAnnotations() map[string]string { if mm.Spec.Ingress == nil { diff --git a/apis/mattermost/v1beta1/zz_generated.deepcopy.go b/apis/mattermost/v1beta1/zz_generated.deepcopy.go index c6e57d7d1..910b8b852 100644 --- a/apis/mattermost/v1beta1/zz_generated.deepcopy.go +++ b/apis/mattermost/v1beta1/zz_generated.deepcopy.go @@ -13,6 +13,26 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AWSLoadBalancerController) DeepCopyInto(out *AWSLoadBalancerController) { + *out = *in + if in.Hosts != nil { + in, out := &in.Hosts, &out.Hosts + *out = make([]IngressHost, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSLoadBalancerController. +func (in *AWSLoadBalancerController) DeepCopy() *AWSLoadBalancerController { + if in == nil { + return nil + } + out := new(AWSLoadBalancerController) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Database) DeepCopyInto(out *Database) { *out = *in @@ -275,6 +295,11 @@ func (in *MattermostSpec) DeepCopyInto(out *MattermostSpec) { *out = new(Ingress) (*in).DeepCopyInto(*out) } + if in.AWSLoadBalancerController != nil { + in, out := &in.AWSLoadBalancerController, &out.AWSLoadBalancerController + *out = new(AWSLoadBalancerController) + (*in).DeepCopyInto(*out) + } if in.Volumes != nil { in, out := &in.Volumes, &out.Volumes *out = make([]v1.Volume, len(*in)) diff --git a/apis/mattermost/v1beta1/zz_generated.openapi.go b/apis/mattermost/v1beta1/zz_generated.openapi.go index 917aaad81..d89452ded 100644 --- a/apis/mattermost/v1beta1/zz_generated.openapi.go +++ b/apis/mattermost/v1beta1/zz_generated.openapi.go @@ -198,6 +198,11 @@ func schema_mattermost_operator_apis_mattermost_v1beta1_MattermostSpec(ref commo Ref: ref("github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.Ingress"), }, }, + "awsLoadBalancerController": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.AWSLoadBalancerController"), + }, + }, "volumes": { SchemaProps: spec.SchemaProps{ Description: "Volumes allows for mounting volumes from various sources into the Mattermost application pods.", @@ -322,6 +327,6 @@ func schema_mattermost_operator_apis_mattermost_v1beta1_MattermostSpec(ref commo }, }, Dependencies: []string{ - "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.Database", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.ElasticSearch", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.FileStore", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.Ingress", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.PodExtensions", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.PodTemplate", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.Probes", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.ResourcePatch", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.Scheduling", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.UpdateJob", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount"}, + "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.AWSLoadBalancerController", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.Database", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.ElasticSearch", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.FileStore", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.Ingress", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.PodExtensions", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.PodTemplate", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.Probes", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.ResourcePatch", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.Scheduling", "github.com/mattermost/mattermost-operator/apis/mattermost/v1beta1.UpdateJob", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount"}, } } diff --git a/config/crd/bases/installation.mattermost.com_mattermosts.yaml b/config/crd/bases/installation.mattermost.com_mattermosts.yaml index d913fb515..a7f1f92cd 100644 --- a/config/crd/bases/installation.mattermost.com_mattermosts.yaml +++ b/config/crd/bases/installation.mattermost.com_mattermosts.yaml @@ -55,6 +55,32 @@ spec: spec: description: MattermostSpec defines the desired state of Mattermost properties: + awsLoadBalancerController: + properties: + certificateARN: + description: Certificate arn for the ALB, required if SSL enabled + type: string + enabled: + description: An AWS ALB Ingress will be created instead of nginx + type: boolean + hosts: + description: Hosts allows specifying additional domain names for + Mattermost to use. + items: + description: IngressHost specifies additional hosts configuration. + properties: + hostName: + type: string + type: object + type: array + ingressClassName: + description: IngressClassName for your ingress + type: string + internetFacing: + description: Whether the Ingress will be internetfacing, default + is false + type: boolean + type: object database: description: External Services properties: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3bd68bf9f..e3a553ed1 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -65,6 +65,7 @@ rules: - networking.k8s.io resources: - ingresses + - ingressclasses verbs: - '*' - apiGroups: diff --git a/controllers/mattermost/mattermost/mattermost.go b/controllers/mattermost/mattermost/mattermost.go index 10ca2105e..aa96664e4 100644 --- a/controllers/mattermost/mattermost/mattermost.go +++ b/controllers/mattermost/mattermost/mattermost.go @@ -53,6 +53,11 @@ func (r *MattermostReconciler) checkMattermost( } if !mattermost.Spec.UseServiceLoadBalancer { + err = r.checkMattermostIngressClass(mattermost, reqLogger) + if err != nil { + return reconcileStatus{}, err + } + err = r.checkMattermostIngress(mattermost, reqLogger) if err != nil { return reconcileStatus{}, err @@ -187,7 +192,11 @@ func (r *MattermostReconciler) checkMattermostRoleBinding(mattermost *mmv1beta.M func (r *MattermostReconciler) checkMattermostIngress(mattermost *mmv1beta.Mattermost, reqLogger logr.Logger) error { desired := mattermostApp.GenerateIngressV1Beta(mattermost) - if !mattermost.IngressEnabled() { + if mattermost.AWSLoadBalancerEnabled() { + desired = mattermostApp.GenerateALBIngressV1Beta(mattermost) + } + + if !mattermost.IngressEnabled() && !mattermost.AWSLoadBalancerEnabled() { err := r.Resources.DeleteIngress(types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name}, reqLogger) if err != nil { return errors.Wrap(err, "failed to delete disabled ingress") @@ -209,13 +218,37 @@ func (r *MattermostReconciler) checkMattermostIngress(mattermost *mmv1beta.Matte return r.Resources.Update(current, desired, reqLogger) } +func (r *MattermostReconciler) checkMattermostIngressClass(mattermost *mmv1beta.Mattermost, reqLogger logr.Logger) error { + desired := mattermostApp.GenerateALBIngressClassV1Beta(mattermost) + + if !mattermost.AWSLoadBalancerEnabled() || mattermost.Spec.AWSLoadBalancerController.IngressClassName != "" { + err := r.Resources.DeleteIngressClass(types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name}, reqLogger) + if err != nil { + return errors.Wrap(err, "failed to delete disabled ingressClass") + } + return nil + } + + err := r.Resources.CreateIngressClassIfNotExists(mattermost, desired, reqLogger) + if err != nil { + return err + } + + current := &networkingv1.IngressClass{} + err = r.Client.Get(context.TODO(), types.NamespacedName{Name: desired.Name, Namespace: desired.Namespace}, current) + if err != nil { + return err + } + + return r.Resources.Update(current, desired, reqLogger) +} + func (r *MattermostReconciler) checkMattermostDeployment( mattermost *mmv1beta.Mattermost, dbConfig mattermostApp.DatabaseConfig, fsConfig mattermostApp.FileStoreConfig, status *mmv1beta.MattermostStatus, reqLogger logr.Logger) (reconcileStatus, error) { - desired := mattermostApp.GenerateDeploymentV1Beta( mattermost, dbConfig, diff --git a/controllers/mattermost/mattermost/mattermost_test.go b/controllers/mattermost/mattermost/mattermost_test.go index 5a769f8cc..1513332c8 100644 --- a/controllers/mattermost/mattermost/mattermost_test.go +++ b/controllers/mattermost/mattermost/mattermost_test.go @@ -5,6 +5,8 @@ import ( "fmt" "testing" + pkgUtils "github.com/mattermost/mattermost-operator/pkg/utils" + "github.com/go-logr/logr" "github.com/sirupsen/logrus" k8sErrors "k8s.io/apimachinery/pkg/api/errors" @@ -390,6 +392,183 @@ func TestCheckMattermost(t *testing.T) { }) } +func TestCheckMattermostAWSLoadBalancer(t *testing.T) { + logger, _, reconciler := setupTestDeps(t) + + mmName := "foo" + mmNamespace := "default" + replicas := int32(4) + mm := &mmv1beta.Mattermost{ + ObjectMeta: metav1.ObjectMeta{ + Name: mmName, + Namespace: mmNamespace, + UID: types.UID("test"), + }, + Spec: mmv1beta.MattermostSpec{ + Replicas: &replicas, + Image: "mattermost/mattermost-enterprise-edition", + Version: operatortest.LatestStableMattermostVersion, + }, + } + + currentMMStatus := &mmv1beta.MattermostStatus{} + + var err error + + t.Run("service", func(t *testing.T) { + mm.Spec.AWSLoadBalancerController = &mmv1beta.AWSLoadBalancerController{ + Enabled: true, + Hosts: []mmv1beta.IngressHost{ + { + HostName: "test.example.com", + }, + }, + } + + err = reconciler.checkMattermostService(mm, currentMMStatus, logger) + assert.NoError(t, err) + + found := &corev1.Service{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, found) + require.NoError(t, err) + require.NotNil(t, found) + + original := found.DeepCopy() + modified := found.DeepCopy() + modified.Labels = nil + modified.Annotations = nil + modified.Spec = corev1.ServiceSpec{} + + err = reconciler.Client.Update(context.TODO(), modified) + require.NoError(t, err) + err = reconciler.checkMattermostService(mm, currentMMStatus, logger) + require.NoError(t, err) + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, found) + require.NoError(t, err) + assert.Equal(t, original.GetName(), found.GetName()) + assert.Equal(t, original.GetNamespace(), found.GetNamespace()) + assert.Equal(t, original.Spec.Selector, found.Spec.Selector) + assert.Equal(t, original.Spec.Ports, found.Spec.Ports) + assert.Equal(t, corev1.ServiceTypeNodePort, found.Spec.Type) + }) + + t.Run("ingress with tls", func(t *testing.T) { + mm.Spec.AWSLoadBalancerController = &mmv1beta.AWSLoadBalancerController{ + Enabled: true, + Hosts: []mmv1beta.IngressHost{ + { + HostName: "test.example.com", + }, + }, + } + mm.Spec.AWSLoadBalancerController.CertificateARN = "test-arn" + + err = reconciler.checkMattermostIngress(mm, logger) + assert.NoError(t, err) + + found := &v1beta1.Ingress{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, found) + require.NoError(t, err) + require.NotNil(t, found) + + original := found.DeepCopy() + modified := found.DeepCopy() + modified.Labels = nil + modified.Annotations = nil + modified.Spec = v1beta1.IngressSpec{} + + err = reconciler.Client.Update(context.TODO(), modified) + require.NoError(t, err) + err = reconciler.checkMattermostIngress(mm, logger) + require.NoError(t, err) + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, found) + require.NoError(t, err) + assert.Equal(t, original.GetAnnotations(), found.GetAnnotations()) + assert.Equal(t, original.GetName(), found.GetName()) + assert.Equal(t, original.GetNamespace(), found.GetNamespace()) + assert.Equal(t, original.Spec.Rules, found.Spec.Rules) + assert.Contains(t, found.Annotations, "alb.ingress.kubernetes.io/scheme") + assert.Contains(t, found.Annotations, "alb.ingress.kubernetes.io/certificate-arn") + assert.Contains(t, found.Annotations, "alb.ingress.kubernetes.io/ssl-redirect") + assert.Contains(t, found.Annotations, "alb.ingress.kubernetes.io/listen-ports") + }) + + t.Run("ingress with specific ingress class", func(t *testing.T) { + mm.Spec.AWSLoadBalancerController = &mmv1beta.AWSLoadBalancerController{ + Enabled: true, + Hosts: []mmv1beta.IngressHost{ + { + HostName: "test.example.com", + }, + }, + } + mm.Spec.AWSLoadBalancerController.IngressClassName = "testClass" + + err = reconciler.checkMattermostIngress(mm, logger) + assert.NoError(t, err) + + err = reconciler.checkMattermostIngressClass(mm, logger) + assert.NoError(t, err) + + found := &v1beta1.Ingress{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, found) + require.NoError(t, err) + require.NotNil(t, found) + + assert.Equal(t, found.Spec.IngressClassName, pkgUtils.NewString("testClass")) + + foundIngressClass := &v1beta1.IngressClass{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, foundIngressClass) + require.Error(t, err) + }) + + t.Run("disable and enable aws load balancer", func(t *testing.T) { + mm.Spec.AWSLoadBalancerController.Enabled = false + err = reconciler.checkMattermostIngress(mm, logger) + assert.NoError(t, err) + + err = reconciler.checkMattermostIngressClass(mm, logger) + assert.NoError(t, err) + + found := &v1beta1.Ingress{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, found) + require.NoError(t, err) + require.NotNil(t, found) + + assert.Contains(t, found.Annotations, "kubernetes.io/ingress.class") + + foundIngressClass := &v1beta1.IngressClass{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, foundIngressClass) + require.Error(t, err) + + mm.Spec.AWSLoadBalancerController = &mmv1beta.AWSLoadBalancerController{ + Enabled: true, + Hosts: []mmv1beta.IngressHost{ + { + HostName: "test.example.com", + }, + }, + } + + err = reconciler.checkMattermostIngress(mm, logger) + assert.NoError(t, err) + + err = reconciler.checkMattermostIngressClass(mm, logger) + assert.NoError(t, err) + + modified := &v1beta1.Ingress{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, modified) + require.NoError(t, err) + require.NotNil(t, modified) + + assert.Contains(t, modified.Annotations, "alb.ingress.kubernetes.io/scheme") + assert.Contains(t, modified.Annotations, "alb.ingress.kubernetes.io/listen-ports") + modifiedIngressClass := &v1beta1.IngressClass{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: mmName, Namespace: mmNamespace}, modifiedIngressClass) + require.NoError(t, err) + }) +} + func TestCheckMattermostUpdateJob(t *testing.T) { logger, _, reconciler := setupTestDeps(t) diff --git a/pkg/mattermost/mattermost.go b/pkg/mattermost/mattermost.go index bae75e9c2..850213875 100644 --- a/pkg/mattermost/mattermost.go +++ b/pkg/mattermost/mattermost.go @@ -295,7 +295,7 @@ func GenerateDeployment(mattermost *mattermostv1alpha1.ClusterInstallation, dbIn // TODO: DB setup job is temporarily disabled as `mattermost version` command // does not account for the custom configuration // Add init container to wait for DB setup job to complete - //initContainers = append(initContainers, waitForSetupJobContainer()) + // initContainers = append(initContainers, waitForSetupJobContainer()) // ES section vars envVarES := []corev1.EnvVar{} diff --git a/pkg/mattermost/mattermost_v1beta.go b/pkg/mattermost/mattermost_v1beta.go index 92f8bdb2c..22929f84b 100644 --- a/pkg/mattermost/mattermost_v1beta.go +++ b/pkg/mattermost/mattermost_v1beta.go @@ -39,6 +39,12 @@ func GenerateServiceV1Beta(mattermost *mmv1beta.Mattermost) *corev1.Service { "service.alpha.kubernetes.io/tolerate-unready-endpoints": "true", } + if mattermost.AWSLoadBalancerEnabled() { + // Create a NodePort service because the ALB requires it + service := newServiceV1Beta(mattermost, mergeStringMaps(baseAnnotations, mattermost.Spec.ServiceAnnotations)) + return configureMattermostServiceNodePort(service) + } + if mattermost.Spec.UseServiceLoadBalancer { // Create a LoadBalancer service with additional annotations provided in // the Mattermost Spec. The LoadBalancer is directly accessible from @@ -74,6 +80,21 @@ func configureMattermostLoadBalancerService(service *corev1.Service) *corev1.Ser } func configureMattermostService(service *corev1.Service) *corev1.Service { + service = configureMattermostServicePorts(service) + service.Spec.ClusterIP = corev1.ClusterIPNone + service.Spec.Type = corev1.ServiceTypeClusterIP + + return service +} + +func configureMattermostServiceNodePort(service *corev1.Service) *corev1.Service { + service = configureMattermostServicePorts(service) + service.Spec.Type = corev1.ServiceTypeNodePort + + return service +} + +func configureMattermostServicePorts(service *corev1.Service) *corev1.Service { service.Spec.Ports = []corev1.ServicePort{ { Port: 8065, @@ -88,8 +109,6 @@ func configureMattermostService(service *corev1.Service) *corev1.Service { TargetPort: intstr.FromString("metrics"), }, } - service.Spec.ClusterIP = corev1.ClusterIPNone - service.Spec.Type = corev1.ServiceTypeClusterIP return service } @@ -144,6 +163,68 @@ func GenerateIngressV1Beta(mattermost *mmv1beta.Mattermost) *networkingv1.Ingres return ingress } +func GenerateALBIngressClassV1Beta(mattermost *mmv1beta.Mattermost) *networkingv1.IngressClass { + ingressClass := &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: mattermost.Name, + Namespace: mattermost.Namespace, + Labels: mattermost.MattermostLabels(mattermost.Name), + OwnerReferences: MattermostOwnerReference(mattermost), + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "ingress.k8s.aws/alb", + }, + } + + return ingressClass +} + +// GenerateIngressALBIngressV1Beta returns the AWS ALB ingress for the Mattermost app. +func GenerateALBIngressV1Beta(mattermost *mmv1beta.Mattermost) *networkingv1.Ingress { + ingressAnnotations := map[string]string{} + + if mattermost.Spec.AWSLoadBalancerController.InternetFacing { + ingressAnnotations["alb.ingress.kubernetes.io/scheme"] = "internet-facing" + } else { + ingressAnnotations["alb.ingress.kubernetes.io/scheme"] = "internal" + } + + if mattermost.Spec.AWSLoadBalancerController.CertificateARN != "" { + ingressAnnotations["alb.ingress.kubernetes.io/certificate-arn"] = mattermost.Spec.AWSLoadBalancerController.CertificateARN + ingressAnnotations["alb.ingress.kubernetes.io/ssl-redirect"] = "443" + ingressAnnotations["alb.ingress.kubernetes.io/listen-ports"] = `[{"HTTP": 80}, {"HTTPS":443}]` + } else { + ingressAnnotations["alb.ingress.kubernetes.io/listen-ports"] = `[{"HTTP": 8065}]` + } + + for k, v := range mattermost.GetIngresAnnotations() { + ingressAnnotations[k] = v + } + + hosts := mattermost.GetAWSLoadBalancerHostNames() + + ingress := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: mattermost.Name, + Namespace: mattermost.Namespace, + Labels: mattermost.MattermostLabels(mattermost.Name), + OwnerReferences: MattermostOwnerReference(mattermost), + Annotations: ingressAnnotations, + }, + Spec: networkingv1.IngressSpec{ + Rules: makeIngressRules(hosts, mattermost), + }, + } + + if mattermost.Spec.AWSLoadBalancerController.IngressClassName != "" { + ingress.Spec.IngressClassName = pkgUtils.NewString(mattermost.Spec.AWSLoadBalancerController.IngressClassName) + } else { + ingress.Spec.IngressClassName = pkgUtils.NewString(mattermost.Name) + } + + return ingress +} + func makeIngressRules(hosts []string, mattermost *mmv1beta.Mattermost) []networkingv1.IngressRule { rules := make([]networkingv1.IngressRule, 0, len(hosts)) for _, host := range hosts { @@ -199,7 +280,7 @@ func GenerateDeploymentV1Beta(mattermost *mmv1beta.Mattermost, db DatabaseConfig // TODO: DB setup job is temporarily disabled as `mattermost version` command // does not account for the custom configuration // Add init container to wait for DB setup job to complete - //initContainers = append(initContainers, waitForSetupJobContainer()) + // initContainers = append(initContainers, waitForSetupJobContainer()) // ES section vars envVarES := []corev1.EnvVar{} diff --git a/pkg/resources/create_resources.go b/pkg/resources/create_resources.go index 6855d5522..8feb6a146 100644 --- a/pkg/resources/create_resources.go +++ b/pkg/resources/create_resources.go @@ -135,6 +135,20 @@ func (r *ResourceHelper) CreateIngressIfNotExists(owner v1.Object, ingress *netw return nil } +func (r *ResourceHelper) CreateIngressClassIfNotExists(owner v1.Object, ingressClass *networkingv1.IngressClass, reqLogger logr.Logger) error { + foundIngressClass := &networkingv1.IngressClass{} + + err := r.client.Get(context.TODO(), types.NamespacedName{Name: ingressClass.Name, Namespace: ingressClass.Namespace}, foundIngressClass) + if err != nil && k8sErrors.IsNotFound(err) { + reqLogger.Info("Creating ingressClass", "name", ingressClass.Name) + return r.Create(owner, ingressClass, reqLogger) + } else if err != nil { + return errors.Wrap(err, "failed to check if ingressClass exists") + } + + return nil +} + func (r *ResourceHelper) CreateDeploymentIfNotExists(owner v1.Object, deployment *appsv1.Deployment, reqLogger logr.Logger) error { foundDeployment := &appsv1.Deployment{} err := r.client.Get(context.TODO(), types.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, foundDeployment) @@ -161,6 +175,24 @@ func (r *ResourceHelper) CreateRoleIfNotExists(owner v1.Object, role *rbacv1.Rol return nil } +func (r *ResourceHelper) DeleteIngressClass(key types.NamespacedName, reqLogger logr.Logger) error { + foundIngressClass := &networkingv1.IngressClass{} + err := r.client.Get(context.TODO(), key, foundIngressClass) + if err != nil && k8sErrors.IsNotFound(err) { + return nil + } else if err != nil { + return errors.Wrap(err, "failed to check if ingressClass exists") + } + + reqLogger.Info("Deleting ingressClass", "name", foundIngressClass.Name) + err = r.client.Delete(context.TODO(), foundIngressClass) + if err != nil { + return errors.Wrap(err, "failed to delete ingressClass") + } + + return nil +} + func (r *ResourceHelper) CreatePvcIfNotExists(owner v1.Object, pvc *corev1.PersistentVolumeClaim, reqLogger logr.Logger) error { foundPvc := &corev1.PersistentVolumeClaim{} err := r.client.Get(context.TODO(), types.NamespacedName{Name: pvc.Name, Namespace: pvc.Namespace}, foundPvc)