github.com/argoproj/argo-events@v1.9.1/controllers/eventbus/installer/jetstream.go (about)

     1  package installer
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"embed"
     7  	"fmt"
     8  	"strconv"
     9  	"strings"
    10  	"text/template"
    11  	"time"
    12  
    13  	"github.com/spf13/viper"
    14  	"go.uber.org/zap"
    15  	appv1 "k8s.io/api/apps/v1"
    16  	corev1 "k8s.io/api/core/v1"
    17  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    18  	apiresource "k8s.io/apimachinery/pkg/api/resource"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/labels"
    21  	"k8s.io/apimachinery/pkg/runtime/schema"
    22  	"k8s.io/apimachinery/pkg/util/intstr"
    23  	"k8s.io/client-go/kubernetes"
    24  	"sigs.k8s.io/controller-runtime/pkg/client"
    25  	"sigs.k8s.io/yaml"
    26  
    27  	"github.com/argoproj/argo-events/common"
    28  	"github.com/argoproj/argo-events/common/tls"
    29  	"github.com/argoproj/argo-events/controllers"
    30  	"github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1"
    31  )
    32  
    33  const (
    34  	jsClientPort  = int32(4222)
    35  	jsClusterPort = int32(6222)
    36  	jsMonitorPort = int32(8222)
    37  	jsMetricsPort = int32(7777)
    38  )
    39  
    40  var (
    41  	//go:embed assets/jetstream/*
    42  	jetStremAssets embed.FS
    43  )
    44  
    45  const (
    46  	secretServerKeyPEMFile  = "server-key.pem"
    47  	secretServerCertPEMFile = "server-cert.pem"
    48  	secretCACertPEMFile     = "ca-cert.pem"
    49  
    50  	secretClusterKeyPEMFile    = "cluster-server-key.pem"
    51  	secretClusterCertPEMFile   = "cluster-server-cert.pem"
    52  	secretClusterCACertPEMFile = "cluster-ca-cert.pem"
    53  
    54  	certOrg = "io.argoproj"
    55  )
    56  
    57  type jetStreamInstaller struct {
    58  	client     client.Client
    59  	eventBus   *v1alpha1.EventBus
    60  	kubeClient kubernetes.Interface
    61  	config     *controllers.GlobalConfig
    62  	labels     map[string]string
    63  	logger     *zap.SugaredLogger
    64  }
    65  
    66  func NewJetStreamInstaller(client client.Client, eventBus *v1alpha1.EventBus, config *controllers.GlobalConfig, labels map[string]string, kubeClient kubernetes.Interface, logger *zap.SugaredLogger) Installer {
    67  	return &jetStreamInstaller{
    68  		client:     client,
    69  		kubeClient: kubeClient,
    70  		eventBus:   eventBus,
    71  		config:     config,
    72  		labels:     labels,
    73  		logger:     logger.With("eventbus", eventBus.Name),
    74  	}
    75  }
    76  
    77  func (r *jetStreamInstaller) Install(ctx context.Context) (*v1alpha1.BusConfig, error) {
    78  	if js := r.eventBus.Spec.JetStream; js == nil {
    79  		return nil, fmt.Errorf("invalid jetstream eventbus spec")
    80  	}
    81  	// merge
    82  	v := viper.New()
    83  	v.SetConfigType("yaml")
    84  	if err := v.ReadConfig(bytes.NewBufferString(r.config.EventBus.JetStream.StreamConfig)); err != nil {
    85  		return nil, fmt.Errorf("invalid jetstream config in global configuration, %w", err)
    86  	}
    87  	if x := r.eventBus.Spec.JetStream.StreamConfig; x != nil {
    88  		if err := v.MergeConfig(bytes.NewBufferString(*x)); err != nil {
    89  			return nil, fmt.Errorf("failed to merge customized stream config, %w", err)
    90  		}
    91  	}
    92  	b, err := yaml.Marshal(v.AllSettings())
    93  	if err != nil {
    94  		return nil, fmt.Errorf("failed to marshal merged buffer config, %w", err)
    95  	}
    96  
    97  	if err := r.createSecrets(ctx); err != nil {
    98  		r.logger.Errorw("failed to create jetstream auth secrets", zap.Error(err))
    99  		r.eventBus.Status.MarkDeployFailed("JetStreamAuthSecretsFailed", err.Error())
   100  		return nil, err
   101  	}
   102  	if err := r.createConfigMap(ctx); err != nil {
   103  		r.logger.Errorw("failed to create jetstream ConfigMap", zap.Error(err))
   104  		r.eventBus.Status.MarkDeployFailed("JetStreamConfigMapFailed", err.Error())
   105  		return nil, err
   106  	}
   107  	if err := r.createService(ctx); err != nil {
   108  		r.logger.Errorw("failed to create jetstream Service", zap.Error(err))
   109  		r.eventBus.Status.MarkDeployFailed("JetStreamServiceFailed", err.Error())
   110  		return nil, err
   111  	}
   112  	if err := r.createStatefulSet(ctx); err != nil {
   113  		r.logger.Errorw("failed to create jetstream StatefulSet", zap.Error(err))
   114  		r.eventBus.Status.MarkDeployFailed("JetStreamStatefulSetFailed", err.Error())
   115  		return nil, err
   116  	}
   117  	r.eventBus.Status.MarkDeployed("Succeeded", "JetStream is deployed")
   118  	return &v1alpha1.BusConfig{
   119  		JetStream: &v1alpha1.JetStreamConfig{
   120  			URL: fmt.Sprintf("nats://%s.%s.svc:%s", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace, strconv.Itoa(int(jsClientPort))),
   121  			AccessSecret: &corev1.SecretKeySelector{
   122  				LocalObjectReference: corev1.LocalObjectReference{
   123  					Name: generateJetStreamClientAuthSecretName(r.eventBus),
   124  				},
   125  				Key: common.JetStreamClientAuthSecretKey,
   126  			},
   127  			StreamConfig: string(b),
   128  		},
   129  	}, nil
   130  }
   131  
   132  // buildJetStreamService builds a Service for Jet Stream
   133  func (r *jetStreamInstaller) buildJetStreamServiceSpec() corev1.ServiceSpec {
   134  	return corev1.ServiceSpec{
   135  		Ports: []corev1.ServicePort{
   136  			{Name: "tcp-client", Port: jsClientPort},
   137  			{Name: "cluster", Port: jsClusterPort},
   138  			{Name: "metrics", Port: jsMetricsPort},
   139  			{Name: "monitor", Port: jsMonitorPort},
   140  		},
   141  		Type:                     corev1.ServiceTypeClusterIP,
   142  		ClusterIP:                corev1.ClusterIPNone,
   143  		PublishNotReadyAddresses: true,
   144  		Selector:                 r.labels,
   145  	}
   146  }
   147  
   148  func (r *jetStreamInstaller) createService(ctx context.Context) error {
   149  	spec := r.buildJetStreamServiceSpec()
   150  	hash := common.MustHash(spec)
   151  	obj := &corev1.Service{
   152  		ObjectMeta: metav1.ObjectMeta{
   153  			Namespace: r.eventBus.Namespace,
   154  			Name:      generateJetStreamServiceName(r.eventBus),
   155  			Labels:    r.labels,
   156  			Annotations: map[string]string{
   157  				common.AnnotationResourceSpecHash: hash,
   158  			},
   159  			OwnerReferences: []metav1.OwnerReference{
   160  				*metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind),
   161  			},
   162  		},
   163  		Spec: spec,
   164  	}
   165  	old := &corev1.Service{}
   166  	if err := r.client.Get(ctx, client.ObjectKeyFromObject(obj), old); err != nil {
   167  		if apierrors.IsNotFound(err) {
   168  			if err := r.client.Create(ctx, obj); err != nil {
   169  				return fmt.Errorf("failed to create jetstream service, err: %w", err)
   170  			}
   171  			r.logger.Info("created jetstream service successfully")
   172  			return nil
   173  		} else {
   174  			return fmt.Errorf("failed to check if jetstream service is existing, err: %w", err)
   175  		}
   176  	}
   177  	if old.GetAnnotations()[common.AnnotationResourceSpecHash] != hash {
   178  		old.Annotations[common.AnnotationResourceSpecHash] = hash
   179  		old.Spec = spec
   180  		if err := r.client.Update(ctx, old); err != nil {
   181  			return fmt.Errorf("failed to update jetstream service, err: %w", err)
   182  		}
   183  		r.logger.Info("updated jetstream service successfully")
   184  	}
   185  	return nil
   186  }
   187  
   188  func (r *jetStreamInstaller) createStatefulSet(ctx context.Context) error {
   189  	jsVersion, err := r.config.GetJetStreamVersion(r.eventBus.Spec.JetStream.Version)
   190  	if err != nil {
   191  		return fmt.Errorf("failed to get jetstream version, err: %w", err)
   192  	}
   193  	spec := r.buildStatefulSetSpec(jsVersion)
   194  	hash := common.MustHash(spec)
   195  	obj := &appv1.StatefulSet{
   196  		ObjectMeta: metav1.ObjectMeta{
   197  			Namespace: r.eventBus.Namespace,
   198  			Name:      generateJetStreamStatefulSetName(r.eventBus),
   199  			Labels:    r.mergeEventBusLabels(r.labels),
   200  			Annotations: map[string]string{
   201  				common.AnnotationResourceSpecHash: hash,
   202  			},
   203  			OwnerReferences: []metav1.OwnerReference{
   204  				*metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind),
   205  			},
   206  		},
   207  		Spec: spec,
   208  	}
   209  	old := &appv1.StatefulSet{}
   210  	if err := r.client.Get(ctx, client.ObjectKeyFromObject(obj), old); err != nil {
   211  		if apierrors.IsNotFound(err) {
   212  			if err := r.client.Create(ctx, obj); err != nil {
   213  				return fmt.Errorf("failed to create jetstream statefulset, err: %w", err)
   214  			}
   215  			r.logger.Info("created jetstream statefulset successfully")
   216  			return nil
   217  		} else {
   218  			return fmt.Errorf("failed to check if jetstream statefulset is existing, err: %w", err)
   219  		}
   220  	}
   221  	if old.GetAnnotations()[common.AnnotationResourceSpecHash] != hash {
   222  		old.Annotations[common.AnnotationResourceSpecHash] = hash
   223  		old.Spec = spec
   224  		if err := r.client.Update(ctx, old); err != nil {
   225  			return fmt.Errorf("failed to update jetstream statefulset, err: %w", err)
   226  		}
   227  		r.logger.Info("updated jetstream statefulset successfully")
   228  	}
   229  	return nil
   230  }
   231  
   232  func (r *jetStreamInstaller) buildStatefulSetSpec(jsVersion *controllers.JetStreamVersion) appv1.StatefulSetSpec {
   233  	js := r.eventBus.Spec.JetStream
   234  	replicas := int32(js.GetReplicas())
   235  	podTemplateLabels := make(map[string]string)
   236  	if js.Metadata != nil &&
   237  		len(js.Metadata.Labels) > 0 {
   238  		for k, v := range js.Metadata.Labels {
   239  			podTemplateLabels[k] = v
   240  		}
   241  	}
   242  	for k, v := range r.labels {
   243  		podTemplateLabels[k] = v
   244  	}
   245  	var jsContainerPullPolicy, reloaderContainerPullPolicy, metricsContainerPullPolicy corev1.PullPolicy
   246  	var jsContainerSecurityContext, reloaderContainerSecurityContext, metricsContainerSecurityContext *corev1.SecurityContext
   247  	if js.ContainerTemplate != nil {
   248  		jsContainerPullPolicy = js.ContainerTemplate.ImagePullPolicy
   249  		jsContainerSecurityContext = js.ContainerTemplate.SecurityContext
   250  	}
   251  	if js.ReloaderContainerTemplate != nil {
   252  		reloaderContainerPullPolicy = js.ReloaderContainerTemplate.ImagePullPolicy
   253  		reloaderContainerSecurityContext = js.ReloaderContainerTemplate.SecurityContext
   254  	}
   255  	if js.MetricsContainerTemplate != nil {
   256  		metricsContainerPullPolicy = js.MetricsContainerTemplate.ImagePullPolicy
   257  		metricsContainerSecurityContext = js.MetricsContainerTemplate.SecurityContext
   258  	}
   259  	shareProcessNamespace := true
   260  	terminationGracePeriodSeconds := int64(60)
   261  	spec := appv1.StatefulSetSpec{
   262  		PodManagementPolicy: appv1.ParallelPodManagement,
   263  		Replicas:            &replicas,
   264  		ServiceName:         generateJetStreamServiceName(r.eventBus),
   265  		Selector: &metav1.LabelSelector{
   266  			MatchLabels: r.labels,
   267  		},
   268  		Template: corev1.PodTemplateSpec{
   269  			ObjectMeta: metav1.ObjectMeta{
   270  				Labels: podTemplateLabels,
   271  			},
   272  			Spec: corev1.PodSpec{
   273  				NodeSelector:                  js.NodeSelector,
   274  				Tolerations:                   js.Tolerations,
   275  				SecurityContext:               js.SecurityContext,
   276  				ImagePullSecrets:              js.ImagePullSecrets,
   277  				PriorityClassName:             js.PriorityClassName,
   278  				Priority:                      js.Priority,
   279  				ServiceAccountName:            js.ServiceAccountName,
   280  				Affinity:                      js.Affinity,
   281  				ShareProcessNamespace:         &shareProcessNamespace,
   282  				TerminationGracePeriodSeconds: &terminationGracePeriodSeconds,
   283  				Volumes: []corev1.Volume{
   284  					{Name: "pid", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}},
   285  					{
   286  						Name: "config-volume",
   287  						VolumeSource: corev1.VolumeSource{
   288  							Projected: &corev1.ProjectedVolumeSource{
   289  								Sources: []corev1.VolumeProjection{
   290  									{
   291  										ConfigMap: &corev1.ConfigMapProjection{
   292  											LocalObjectReference: corev1.LocalObjectReference{
   293  												Name: generateJetStreamConfigMapName(r.eventBus),
   294  											},
   295  											Items: []corev1.KeyToPath{
   296  												{
   297  													Key:  common.JetStreamConfigMapKey,
   298  													Path: "nats-js.conf",
   299  												},
   300  											},
   301  										},
   302  									},
   303  									{
   304  										Secret: &corev1.SecretProjection{
   305  											LocalObjectReference: corev1.LocalObjectReference{
   306  												Name: generateJetStreamServerSecretName(r.eventBus),
   307  											},
   308  											Items: []corev1.KeyToPath{
   309  												{
   310  													Key:  common.JetStreamServerSecretAuthKey,
   311  													Path: "auth.conf",
   312  												},
   313  												{
   314  													Key:  common.JetStreamServerPrivateKeyKey,
   315  													Path: secretServerKeyPEMFile,
   316  												},
   317  												{
   318  													Key:  common.JetStreamServerCertKey,
   319  													Path: secretServerCertPEMFile,
   320  												},
   321  												{
   322  													Key:  common.JetStreamServerCACertKey,
   323  													Path: secretCACertPEMFile,
   324  												},
   325  												{
   326  													Key:  common.JetStreamClusterPrivateKeyKey,
   327  													Path: secretClusterKeyPEMFile,
   328  												},
   329  												{
   330  													Key:  common.JetStreamClusterCertKey,
   331  													Path: secretClusterCertPEMFile,
   332  												},
   333  												{
   334  													Key:  common.JetStreamClusterCACertKey,
   335  													Path: secretClusterCACertPEMFile,
   336  												},
   337  											},
   338  										},
   339  									},
   340  								},
   341  							},
   342  						},
   343  					},
   344  				},
   345  				Containers: []corev1.Container{
   346  					{
   347  						Name:            "main",
   348  						Image:           jsVersion.NatsImage,
   349  						ImagePullPolicy: jsContainerPullPolicy,
   350  						Ports: []corev1.ContainerPort{
   351  							{Name: "client", ContainerPort: jsClientPort},
   352  							{Name: "cluster", ContainerPort: jsClusterPort},
   353  							{Name: "monitor", ContainerPort: jsMonitorPort},
   354  						},
   355  						Command: []string{jsVersion.StartCommand, "--config", "/etc/nats-config/nats-js.conf"},
   356  						Args:    js.StartArgs,
   357  						Env: []corev1.EnvVar{
   358  							{Name: "POD_NAME", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}}},
   359  							{Name: "SERVER_NAME", Value: "$(POD_NAME)"},
   360  							{Name: "POD_NAMESPACE", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}}},
   361  							{Name: "CLUSTER_ADVERTISE", Value: "$(POD_NAME)." + generateJetStreamServiceName(r.eventBus) + ".$(POD_NAMESPACE).svc"},
   362  							{Name: "JS_KEY", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: generateJetStreamServerSecretName(r.eventBus)}, Key: common.JetStreamServerSecretEncryptionKey}}},
   363  						},
   364  						VolumeMounts: []corev1.VolumeMount{
   365  							{Name: "config-volume", MountPath: "/etc/nats-config"},
   366  							{Name: "pid", MountPath: "/var/run/nats"},
   367  						},
   368  						SecurityContext: jsContainerSecurityContext,
   369  						StartupProbe: &corev1.Probe{
   370  							ProbeHandler: corev1.ProbeHandler{
   371  								HTTPGet: &corev1.HTTPGetAction{
   372  									Path: "/healthz",
   373  									Port: intstr.FromInt(int(jsMonitorPort)),
   374  								},
   375  							},
   376  							FailureThreshold:    30,
   377  							InitialDelaySeconds: 10,
   378  							TimeoutSeconds:      5,
   379  						},
   380  						LivenessProbe: &corev1.Probe{
   381  							ProbeHandler: corev1.ProbeHandler{
   382  								HTTPGet: &corev1.HTTPGetAction{
   383  									Path: "/",
   384  									Port: intstr.FromInt(int(jsMonitorPort)),
   385  								},
   386  							},
   387  							InitialDelaySeconds: 10,
   388  							PeriodSeconds:       30,
   389  							TimeoutSeconds:      5,
   390  						},
   391  						Lifecycle: &corev1.Lifecycle{
   392  							PreStop: &corev1.LifecycleHandler{
   393  								Exec: &corev1.ExecAction{
   394  									Command: []string{jsVersion.StartCommand, "-sl=ldm=/var/run/nats/nats.pid"},
   395  								},
   396  							},
   397  						},
   398  					},
   399  					{
   400  						Name:            "reloader",
   401  						Image:           jsVersion.ConfigReloaderImage,
   402  						ImagePullPolicy: reloaderContainerPullPolicy,
   403  						SecurityContext: reloaderContainerSecurityContext,
   404  						Command:         []string{"nats-server-config-reloader", "-pid", "/var/run/nats/nats.pid", "-config", "/etc/nats-config/nats-js.conf"},
   405  						VolumeMounts: []corev1.VolumeMount{
   406  							{Name: "config-volume", MountPath: "/etc/nats-config"},
   407  							{Name: "pid", MountPath: "/var/run/nats"},
   408  						},
   409  					},
   410  					{
   411  						Name:            "metrics",
   412  						Image:           jsVersion.MetricsExporterImage,
   413  						ImagePullPolicy: metricsContainerPullPolicy,
   414  						Ports: []corev1.ContainerPort{
   415  							{Name: "metrics", ContainerPort: jsMetricsPort},
   416  						},
   417  						Args:            []string{"-connz", "-routez", "-subz", "-varz", "-prefix=nats", "-use_internal_server_id", "-jsz=all", fmt.Sprintf("http://localhost:%s", strconv.Itoa(int(jsMonitorPort)))},
   418  						SecurityContext: metricsContainerSecurityContext,
   419  					},
   420  				},
   421  			},
   422  		},
   423  	}
   424  	if js.Metadata != nil {
   425  		spec.Template.SetAnnotations(js.Metadata.Annotations)
   426  	}
   427  
   428  	podContainers := spec.Template.Spec.Containers
   429  	containers := map[string]*corev1.Container{}
   430  	for idx := range podContainers {
   431  		containers[podContainers[idx].Name] = &podContainers[idx]
   432  	}
   433  
   434  	if js.ContainerTemplate != nil {
   435  		containers["main"].Resources = js.ContainerTemplate.Resources
   436  	}
   437  
   438  	if js.MetricsContainerTemplate != nil {
   439  		containers["metrics"].Resources = js.MetricsContainerTemplate.Resources
   440  	}
   441  
   442  	if js.ReloaderContainerTemplate != nil {
   443  		containers["reloader"].Resources = js.ReloaderContainerTemplate.Resources
   444  	}
   445  
   446  	if js.Persistence != nil {
   447  		volMode := corev1.PersistentVolumeFilesystem
   448  		// Default volume size
   449  		volSize := apiresource.MustParse("20Gi")
   450  		if js.Persistence.VolumeSize != nil {
   451  			volSize = *js.Persistence.VolumeSize
   452  		}
   453  		// Default to ReadWriteOnce
   454  		accessMode := corev1.ReadWriteOnce
   455  		if js.Persistence.AccessMode != nil {
   456  			accessMode = *js.Persistence.AccessMode
   457  		}
   458  		spec.VolumeClaimTemplates = []corev1.PersistentVolumeClaim{
   459  			{
   460  				ObjectMeta: metav1.ObjectMeta{
   461  					Name: generateJetStreamPVCName(r.eventBus),
   462  				},
   463  				Spec: corev1.PersistentVolumeClaimSpec{
   464  					AccessModes: []corev1.PersistentVolumeAccessMode{
   465  						accessMode,
   466  					},
   467  					VolumeMode:       &volMode,
   468  					StorageClassName: js.Persistence.StorageClassName,
   469  					Resources: corev1.ResourceRequirements{
   470  						Requests: corev1.ResourceList{
   471  							corev1.ResourceStorage: volSize,
   472  						},
   473  					},
   474  				},
   475  			},
   476  		}
   477  		volumeMounts := spec.Template.Spec.Containers[0].VolumeMounts
   478  		volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: generateJetStreamPVCName(r.eventBus), MountPath: "/data/jetstream"})
   479  		spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts
   480  	} else {
   481  		// When the POD is runasnonroot, it can not create the dir /data/jetstream
   482  		// Use an emptyDirVolume
   483  		emptyDirVolName := "js-data"
   484  		volumes := spec.Template.Spec.Volumes
   485  		volumes = append(volumes, corev1.Volume{Name: emptyDirVolName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}})
   486  		spec.Template.Spec.Volumes = volumes
   487  		volumeMounts := spec.Template.Spec.Containers[0].VolumeMounts
   488  		volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: emptyDirVolName, MountPath: "/data/jetstream"})
   489  		spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts
   490  	}
   491  	return spec
   492  }
   493  
   494  func (r *jetStreamInstaller) getSecret(ctx context.Context, name string) (*corev1.Secret, error) {
   495  	sl, err := r.kubeClient.CoreV1().Secrets(r.eventBus.Namespace).List(ctx, metav1.ListOptions{})
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  	for _, s := range sl.Items {
   500  		if s.Name == name && metav1.IsControlledBy(&s, r.eventBus) {
   501  			return &s, nil
   502  		}
   503  	}
   504  	return nil, apierrors.NewNotFound(schema.GroupResource{}, "")
   505  }
   506  
   507  func (r *jetStreamInstaller) createSecrets(ctx context.Context) error {
   508  	// first check to see if the secrets already exist
   509  	oldServerObjExisting, oldClientObjExisting := true, true
   510  
   511  	oldSObj, err := r.getSecret(ctx, generateJetStreamServerSecretName(r.eventBus))
   512  	if err != nil {
   513  		if apierrors.IsNotFound(err) {
   514  			oldServerObjExisting = false
   515  		} else {
   516  			return fmt.Errorf("failed to check if nats server auth secret is existing, err: %w", err)
   517  		}
   518  	}
   519  
   520  	oldCObj, err := r.getSecret(ctx, generateJetStreamClientAuthSecretName(r.eventBus))
   521  	if err != nil {
   522  		if apierrors.IsNotFound(err) {
   523  			oldClientObjExisting = false
   524  		} else {
   525  			return fmt.Errorf("failed to check if nats client auth secret is existing, err: %w", err)
   526  		}
   527  	}
   528  
   529  	if !oldClientObjExisting || !oldServerObjExisting {
   530  		// Generate server-auth.conf file
   531  		encryptionKey := common.RandomString(12)
   532  		jsUser := common.RandomString(8)
   533  		jsPass := common.RandomString(16)
   534  		sysPassword := common.RandomString(24)
   535  		authTpl := template.Must(template.ParseFS(jetStremAssets, "assets/jetstream/server-auth.conf"))
   536  		var authTplOutput bytes.Buffer
   537  		if err := authTpl.Execute(&authTplOutput, struct {
   538  			JetStreamUser     string
   539  			JetStreamPassword string
   540  			SysPassword       string
   541  		}{
   542  			JetStreamUser:     jsUser,
   543  			JetStreamPassword: jsPass,
   544  			SysPassword:       sysPassword,
   545  		}); err != nil {
   546  			return fmt.Errorf("failed to parse nats auth template, error: %w", err)
   547  		}
   548  
   549  		// Generate TLS self signed certificate for Jetstream bus: includes TLS private key, certificate, and CA certificate
   550  		hosts := []string{}
   551  		hosts = append(hosts, fmt.Sprintf("%s.%s.svc.cluster.local", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace)) // todo: get an error in the log file related to this: do we need it?
   552  		hosts = append(hosts, fmt.Sprintf("%s.%s.svc", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace))
   553  
   554  		serverKeyPEM, serverCertPEM, caCertPEM, err := tls.CreateCerts(certOrg, hosts, time.Now().Add(10*365*24*time.Hour), true, false) // expires in 10 years
   555  		if err != nil {
   556  			return err
   557  		}
   558  
   559  		// Generate TLS self signed certificate for Jetstream cluster nodes: includes TLS private key, certificate, and CA certificate
   560  		clusterNodeHosts := []string{
   561  			fmt.Sprintf("*.%s.%s.svc.cluster.local", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace),
   562  			fmt.Sprintf("*.%s.%s.svc", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace),
   563  		}
   564  		r.logger.Infof("cluster node hosts: %+v", clusterNodeHosts)
   565  		clusterKeyPEM, clusterCertPEM, clusterCACertPEM, err := tls.CreateCerts(certOrg, clusterNodeHosts, time.Now().Add(10*365*24*time.Hour), true, true) // expires in 10 years
   566  		if err != nil {
   567  			return err
   568  		}
   569  
   570  		serverObj := &corev1.Secret{
   571  			ObjectMeta: metav1.ObjectMeta{
   572  				Namespace: r.eventBus.Namespace,
   573  				Name:      generateJetStreamServerSecretName(r.eventBus),
   574  				Labels:    r.labels,
   575  				OwnerReferences: []metav1.OwnerReference{
   576  					*metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind),
   577  				},
   578  			},
   579  			Type: corev1.SecretTypeOpaque,
   580  			Data: map[string][]byte{
   581  				common.JetStreamServerSecretAuthKey:       authTplOutput.Bytes(),
   582  				common.JetStreamServerSecretEncryptionKey: []byte(encryptionKey),
   583  				common.JetStreamServerPrivateKeyKey:       serverKeyPEM,
   584  				common.JetStreamServerCertKey:             serverCertPEM,
   585  				common.JetStreamServerCACertKey:           caCertPEM,
   586  				common.JetStreamClusterPrivateKeyKey:      clusterKeyPEM,
   587  				common.JetStreamClusterCertKey:            clusterCertPEM,
   588  				common.JetStreamClusterCACertKey:          clusterCACertPEM,
   589  			},
   590  		}
   591  
   592  		clientAuthObj := &corev1.Secret{
   593  			ObjectMeta: metav1.ObjectMeta{
   594  				Namespace: r.eventBus.Namespace,
   595  				Name:      generateJetStreamClientAuthSecretName(r.eventBus),
   596  				Labels:    r.labels,
   597  				OwnerReferences: []metav1.OwnerReference{
   598  					*metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind),
   599  				},
   600  			},
   601  			Type: corev1.SecretTypeOpaque,
   602  			Data: map[string][]byte{
   603  				common.JetStreamClientAuthSecretKey: []byte(fmt.Sprintf("username: %s\npassword: %s", jsUser, jsPass)),
   604  			},
   605  		}
   606  
   607  		if oldServerObjExisting {
   608  			if err := r.client.Delete(ctx, oldSObj); err != nil {
   609  				return fmt.Errorf("failed to delete malformed nats server auth secret, err: %w", err)
   610  			}
   611  			r.logger.Infow("deleted malformed nats server auth secret successfully")
   612  		}
   613  
   614  		if oldClientObjExisting {
   615  			if err := r.client.Delete(ctx, oldCObj); err != nil {
   616  				return fmt.Errorf("failed to delete malformed nats client auth secret, err: %w", err)
   617  			}
   618  			r.logger.Infow("deleted malformed nats client auth secret successfully")
   619  		}
   620  
   621  		if err := r.client.Create(ctx, serverObj); err != nil {
   622  			return fmt.Errorf("failed to create nats server auth secret, err: %w", err)
   623  		}
   624  		r.logger.Infow("created nats server auth secret successfully")
   625  
   626  		if err := r.client.Create(ctx, clientAuthObj); err != nil {
   627  			return fmt.Errorf("failed to create nats client auth secret, err: %w", err)
   628  		}
   629  		r.logger.Infow("created nats client auth secret successfully")
   630  	}
   631  
   632  	return nil
   633  }
   634  
   635  func (r *jetStreamInstaller) createConfigMap(ctx context.Context) error {
   636  	data := make(map[string]string)
   637  	svcName := generateJetStreamServiceName(r.eventBus)
   638  	ssName := generateJetStreamStatefulSetName(r.eventBus)
   639  	replicas := r.eventBus.Spec.JetStream.GetReplicas()
   640  	routes := []string{}
   641  	for j := 0; j < replicas; j++ {
   642  		routes = append(routes, fmt.Sprintf("nats://%s-%s.%s.%s.svc:%s", ssName, strconv.Itoa(j), svcName, r.eventBus.Namespace, strconv.Itoa(int(jsClusterPort))))
   643  	}
   644  	settings := r.config.EventBus.JetStream.Settings
   645  	if x := r.eventBus.Spec.JetStream.Settings; x != nil {
   646  		settings = *x
   647  	}
   648  	maxPayload := common.JetStreamMaxPayload
   649  	if r.eventBus.Spec.JetStream.MaxPayload != nil {
   650  		maxPayload = *r.eventBus.Spec.JetStream.MaxPayload
   651  	}
   652  	var confTpl *template.Template
   653  	if replicas > 2 {
   654  		confTpl = template.Must(template.ParseFS(jetStremAssets, "assets/jetstream/nats-cluster.conf"))
   655  	} else {
   656  		confTpl = template.Must(template.ParseFS(jetStremAssets, "assets/jetstream/nats.conf"))
   657  	}
   658  	var confTplOutput bytes.Buffer
   659  	if err := confTpl.Execute(&confTplOutput, struct {
   660  		MaxPayloadSize string
   661  		ClusterName    string
   662  		MonitorPort    string
   663  		ClusterPort    string
   664  		ClientPort     string
   665  		Routes         string
   666  		Settings       string
   667  	}{
   668  		MaxPayloadSize: maxPayload,
   669  		ClusterName:    r.eventBus.Name,
   670  		MonitorPort:    strconv.Itoa(int(jsMonitorPort)),
   671  		ClusterPort:    strconv.Itoa(int(jsClusterPort)),
   672  		ClientPort:     strconv.Itoa(int(jsClientPort)),
   673  		Routes:         strings.Join(routes, ","),
   674  		Settings:       settings,
   675  	}); err != nil {
   676  		return fmt.Errorf("failed to parse nats config template, error: %w", err)
   677  	}
   678  	data[common.JetStreamConfigMapKey] = confTplOutput.String()
   679  
   680  	hash := common.MustHash(data)
   681  	obj := &corev1.ConfigMap{
   682  		ObjectMeta: metav1.ObjectMeta{
   683  			Namespace: r.eventBus.Namespace,
   684  			Name:      generateJetStreamConfigMapName(r.eventBus),
   685  			Labels:    r.labels,
   686  			Annotations: map[string]string{
   687  				common.AnnotationResourceSpecHash: hash,
   688  			},
   689  			OwnerReferences: []metav1.OwnerReference{
   690  				*metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind),
   691  			},
   692  		},
   693  		Data: data,
   694  	}
   695  	old := &corev1.ConfigMap{}
   696  	if err := r.client.Get(ctx, client.ObjectKeyFromObject(obj), old); err != nil {
   697  		if apierrors.IsNotFound(err) {
   698  			if err := r.client.Create(ctx, obj); err != nil {
   699  				return fmt.Errorf("failed to create jetstream configmap, err: %w", err)
   700  			}
   701  			r.logger.Info("created jetstream configmap successfully")
   702  			return nil
   703  		} else {
   704  			return fmt.Errorf("failed to check if jetstream configmap is existing, err: %w", err)
   705  		}
   706  	}
   707  	if old.GetAnnotations()[common.AnnotationResourceSpecHash] != hash {
   708  		old.Annotations[common.AnnotationResourceSpecHash] = hash
   709  		old.Data = data
   710  		if err := r.client.Update(ctx, old); err != nil {
   711  			return fmt.Errorf("failed to update jetstream configmap, err: %w", err)
   712  		}
   713  		r.logger.Info("updated jetstream configmap successfully")
   714  	}
   715  	return nil
   716  }
   717  
   718  func (r *jetStreamInstaller) Uninstall(ctx context.Context) error {
   719  	return r.uninstallPVCs(ctx)
   720  }
   721  
   722  func (r *jetStreamInstaller) uninstallPVCs(ctx context.Context) error {
   723  	// StatefulSet doesn't clean up PVC, needs to do it separately
   724  	// https://github.com/kubernetes/kubernetes/issues/55045
   725  	pvcs, err := r.getPVCs(ctx)
   726  	if err != nil {
   727  		r.logger.Errorw("failed to get PVCs created by Nats statefulset when uninstalling", zap.Error(err))
   728  		return err
   729  	}
   730  	for _, pvc := range pvcs {
   731  		err = r.client.Delete(ctx, &pvc)
   732  		if err != nil {
   733  			r.logger.Errorw("failed to delete pvc when uninstalling", zap.Any("pvcName", pvc.Name), zap.Error(err))
   734  			return err
   735  		}
   736  		r.logger.Infow("pvc deleted", "pvcName", pvc.Name)
   737  	}
   738  	return nil
   739  }
   740  
   741  // get PVCs created by streaming statefulset
   742  // they have same labels as the statefulset
   743  func (r *jetStreamInstaller) getPVCs(ctx context.Context) ([]corev1.PersistentVolumeClaim, error) {
   744  	pvcl := &corev1.PersistentVolumeClaimList{}
   745  	err := r.client.List(ctx, pvcl, &client.ListOptions{
   746  		Namespace:     r.eventBus.Namespace,
   747  		LabelSelector: labels.SelectorFromSet(r.labels),
   748  	})
   749  	if err != nil {
   750  		return nil, err
   751  	}
   752  	return pvcl.Items, nil
   753  }
   754  
   755  func generateJetStreamServerSecretName(eventBus *v1alpha1.EventBus) string {
   756  	return fmt.Sprintf("eventbus-%s-js-server", eventBus.Name)
   757  }
   758  
   759  func (r *jetStreamInstaller) mergeEventBusLabels(given map[string]string) map[string]string {
   760  	result := map[string]string{}
   761  	if r.eventBus.Labels != nil {
   762  		for k, v := range r.eventBus.Labels {
   763  			result[k] = v
   764  		}
   765  	}
   766  	for k, v := range given {
   767  		result[k] = v
   768  	}
   769  	return result
   770  }
   771  
   772  func generateJetStreamClientAuthSecretName(eventBus *v1alpha1.EventBus) string {
   773  	return fmt.Sprintf("eventbus-%s-js-client-auth", eventBus.Name)
   774  }
   775  
   776  func generateJetStreamServiceName(eventBus *v1alpha1.EventBus) string {
   777  	return fmt.Sprintf("eventbus-%s-js-svc", eventBus.Name)
   778  }
   779  
   780  func generateJetStreamStatefulSetName(eventBus *v1alpha1.EventBus) string {
   781  	return fmt.Sprintf("eventbus-%s-js", eventBus.Name)
   782  }
   783  
   784  func generateJetStreamConfigMapName(eventBus *v1alpha1.EventBus) string {
   785  	return fmt.Sprintf("eventbus-%s-js-config", eventBus.Name)
   786  }
   787  
   788  func generateJetStreamPVCName(eventBus *v1alpha1.EventBus) string {
   789  	return fmt.Sprintf("eventbus-%s-js-vol", eventBus.Name)
   790  }