github.com/argoproj/argo-events@v1.9.1/controllers/eventsource/resource.go (about)

     1  package eventsource
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/imdario/mergo"
    11  	"go.uber.org/zap"
    12  	appv1 "k8s.io/api/apps/v1"
    13  	corev1 "k8s.io/api/core/v1"
    14  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/labels"
    17  	"k8s.io/apimachinery/pkg/runtime/schema"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	"sigs.k8s.io/controller-runtime/pkg/client"
    20  
    21  	"github.com/argoproj/argo-events/common"
    22  	controllerscommon "github.com/argoproj/argo-events/controllers/common"
    23  	eventbusv1alpha1 "github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1"
    24  	"github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1"
    25  )
    26  
    27  // AdaptorArgs are the args needed to create a sensor deployment
    28  type AdaptorArgs struct {
    29  	Image       string
    30  	EventSource *v1alpha1.EventSource
    31  	Labels      map[string]string
    32  }
    33  
    34  // Reconcile does the real logic
    35  func Reconcile(client client.Client, args *AdaptorArgs, logger *zap.SugaredLogger) error {
    36  	ctx := context.Background()
    37  	eventSource := args.EventSource
    38  	eventBus := &eventbusv1alpha1.EventBus{}
    39  	eventBusName := common.DefaultEventBusName
    40  	if len(eventSource.Spec.EventBusName) > 0 {
    41  		eventBusName = eventSource.Spec.EventBusName
    42  	}
    43  	err := client.Get(ctx, types.NamespacedName{Namespace: eventSource.Namespace, Name: eventBusName}, eventBus)
    44  	if err != nil {
    45  		if apierrors.IsNotFound(err) {
    46  			eventSource.Status.MarkDeployFailed("EventBusNotFound", "EventBus not found.")
    47  			logger.Errorw("EventBus not found", "eventBusName", eventBusName, "error", err)
    48  			return fmt.Errorf("eventbus %s not found", eventBusName)
    49  		}
    50  		eventSource.Status.MarkDeployFailed("GetEventBusFailed", "Failed to get EventBus.")
    51  		logger.Errorw("failed to get EventBus", "eventBusName", eventBusName, "error", err)
    52  		return err
    53  	}
    54  	if !eventBus.Status.IsReady() {
    55  		eventSource.Status.MarkDeployFailed("EventBusNotReady", "EventBus not ready.")
    56  		logger.Errorw("event bus is not in ready status", "eventBusName", eventBusName, "error", err)
    57  		return fmt.Errorf("eventbus not ready")
    58  	}
    59  
    60  	expectedDeploy, err := buildDeployment(args, eventBus)
    61  	if err != nil {
    62  		eventSource.Status.MarkDeployFailed("BuildDeploymentSpecFailed", "Failed to build Deployment spec.")
    63  		logger.Errorw("failed to build deployment spec", "error", err)
    64  		return err
    65  	}
    66  
    67  	deploy, err := getDeployment(ctx, client, args)
    68  	if err != nil && !apierrors.IsNotFound(err) {
    69  		eventSource.Status.MarkDeployFailed("GetDeploymentFailed", "Get existing deployment failed")
    70  		logger.Errorw("error getting existing deployment", "error", err)
    71  		return err
    72  	}
    73  	if deploy != nil {
    74  		if deploy.Annotations != nil && deploy.Annotations[common.AnnotationResourceSpecHash] != expectedDeploy.Annotations[common.AnnotationResourceSpecHash] {
    75  			deploy.Spec = expectedDeploy.Spec
    76  			deploy.SetLabels(expectedDeploy.Labels)
    77  			deploy.Annotations[common.AnnotationResourceSpecHash] = expectedDeploy.Annotations[common.AnnotationResourceSpecHash]
    78  			err = client.Update(ctx, deploy)
    79  			if err != nil {
    80  				eventSource.Status.MarkDeployFailed("UpdateDeploymentFailed", "Failed to update existing deployment")
    81  				logger.Errorw("error updating existing deployment", "error", err)
    82  				return err
    83  			}
    84  			logger.Infow("deployment is updated", "deploymentName", deploy.Name)
    85  		}
    86  	} else {
    87  		err = client.Create(ctx, expectedDeploy)
    88  		if err != nil {
    89  			eventSource.Status.MarkDeployFailed("CreateDeploymentFailed", "Failed to create a deployment")
    90  			logger.Errorw("error creating a deployment", "error", err)
    91  			return err
    92  		}
    93  		logger.Infow("deployment is created", "deploymentName", expectedDeploy.Name)
    94  	}
    95  	// Service if any
    96  	existingSvc, err := getService(ctx, client, args)
    97  	if err != nil && !apierrors.IsNotFound(err) {
    98  		eventSource.Status.MarkDeployFailed("GetServiceFailed", "Failed to get existing service")
    99  		logger.Errorw("error getting existing service", "error", err)
   100  		return err
   101  	}
   102  	expectedSvc, err := buildService(args)
   103  	if err != nil {
   104  		eventSource.Status.MarkDeployFailed("BuildServiceFailed", "Failed to build service spec")
   105  		logger.Errorw("error building service spec", "error", err)
   106  		return err
   107  	}
   108  	if expectedSvc == nil {
   109  		if existingSvc != nil {
   110  			err = client.Delete(ctx, existingSvc)
   111  			if err != nil {
   112  				eventSource.Status.MarkDeployFailed("DeleteServiceFailed", "Failed to delete existing service")
   113  				logger.Errorw("error deleting existing service", "error", err)
   114  				return err
   115  			}
   116  			logger.Infow("deleted existing service", "serviceName", existingSvc.Name)
   117  		}
   118  	} else {
   119  		if existingSvc == nil {
   120  			err = client.Create(ctx, expectedSvc)
   121  			if err != nil {
   122  				eventSource.Status.MarkDeployFailed("CreateServiceFailed", "Failed to create a service")
   123  				logger.Errorw("error creating a service", "error", err)
   124  				return err
   125  			}
   126  			logger.Infow("service is created", "serviceName", expectedSvc.Name)
   127  		} else if existingSvc.Annotations != nil && existingSvc.Annotations[common.AnnotationResourceSpecHash] != expectedSvc.Annotations[common.AnnotationResourceSpecHash] {
   128  			// To avoid service updating issues such as port name change, re-create it.
   129  			err = client.Delete(ctx, existingSvc)
   130  			if err != nil {
   131  				eventSource.Status.MarkDeployFailed("DeleteServiceFailed", "Failed to delete existing service")
   132  				logger.Errorw("error deleting existing service", "error", err)
   133  				return err
   134  			}
   135  			err = client.Create(ctx, expectedSvc)
   136  			if err != nil {
   137  				eventSource.Status.MarkDeployFailed("RecreateServiceFailed", "Failed to re-create existing service")
   138  				logger.Errorw("error re-creating existing service", "error", err)
   139  				return err
   140  			}
   141  			logger.Infow("service is re-created", "serviceName", existingSvc.Name)
   142  		}
   143  	}
   144  	eventSource.Status.MarkDeployed()
   145  	return nil
   146  }
   147  
   148  func getDeployment(ctx context.Context, cl client.Client, args *AdaptorArgs) (*appv1.Deployment, error) {
   149  	dl := &appv1.DeploymentList{}
   150  	err := cl.List(ctx, dl, &client.ListOptions{
   151  		Namespace:     args.EventSource.Namespace,
   152  		LabelSelector: labelSelector(args.Labels),
   153  	})
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	for _, deploy := range dl.Items {
   158  		if metav1.IsControlledBy(&deploy, args.EventSource) {
   159  			return &deploy, nil
   160  		}
   161  	}
   162  	return nil, apierrors.NewNotFound(schema.GroupResource{}, "")
   163  }
   164  
   165  func buildDeployment(args *AdaptorArgs, eventBus *eventbusv1alpha1.EventBus) (*appv1.Deployment, error) {
   166  	deploymentSpec, err := buildDeploymentSpec(args)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	eventSourceCopy := &v1alpha1.EventSource{
   171  		ObjectMeta: metav1.ObjectMeta{
   172  			Namespace: args.EventSource.Namespace,
   173  			Name:      args.EventSource.Name,
   174  		},
   175  		Spec: args.EventSource.Spec,
   176  	}
   177  	eventSourceBytes, err := json.Marshal(eventSourceCopy)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("failed marshal eventsource spec")
   180  	}
   181  	busConfigBytes, err := json.Marshal(eventBus.Status.Config)
   182  	if err != nil {
   183  		return nil, fmt.Errorf("failed marshal event bus config: %v", err)
   184  	}
   185  
   186  	env := []corev1.EnvVar{
   187  		{
   188  			Name:  common.EnvVarEventSourceObject,
   189  			Value: base64.StdEncoding.EncodeToString(eventSourceBytes),
   190  		},
   191  		{
   192  			Name:  common.EnvVarEventBusSubject,
   193  			Value: fmt.Sprintf("eventbus-%s", args.EventSource.Namespace),
   194  		},
   195  		{
   196  			Name:      common.EnvVarPodName,
   197  			ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}},
   198  		},
   199  		{
   200  			Name:  common.EnvVarLeaderElection,
   201  			Value: args.EventSource.Annotations[common.AnnotationLeaderElection],
   202  		},
   203  		{
   204  			Name:  common.EnvVarEventBusConfig,
   205  			Value: base64.StdEncoding.EncodeToString(busConfigBytes),
   206  		},
   207  	}
   208  
   209  	volumes := []corev1.Volume{
   210  		{
   211  			Name:         "tmp",
   212  			VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
   213  		},
   214  	}
   215  
   216  	volumeMounts := []corev1.VolumeMount{
   217  		{
   218  			Name:      "tmp",
   219  			MountPath: "/tmp",
   220  		},
   221  	}
   222  
   223  	var secretObjs []interface{}
   224  	var accessSecret *corev1.SecretKeySelector
   225  	switch {
   226  	case eventBus.Status.Config.NATS != nil:
   227  		accessSecret = eventBus.Status.Config.NATS.AccessSecret
   228  		secretObjs = []interface{}{eventSourceCopy}
   229  	case eventBus.Status.Config.JetStream != nil:
   230  		accessSecret = eventBus.Status.Config.JetStream.AccessSecret
   231  		secretObjs = []interface{}{eventSourceCopy}
   232  	case eventBus.Status.Config.Kafka != nil:
   233  		accessSecret = nil
   234  		secretObjs = []interface{}{eventSourceCopy, eventBus} // kafka requires secrets for sasl and tls
   235  	default:
   236  		return nil, fmt.Errorf("unsupported event bus")
   237  	}
   238  
   239  	if accessSecret != nil {
   240  		// Mount the secret as volume instead of using envFrom to gain the ability
   241  		// for the sensor deployment to auto reload when the secret changes
   242  		volumes = append(volumes, corev1.Volume{
   243  			Name: "auth-volume",
   244  			VolumeSource: corev1.VolumeSource{
   245  				Secret: &corev1.SecretVolumeSource{
   246  					SecretName: accessSecret.Name,
   247  					Items: []corev1.KeyToPath{
   248  						{
   249  							Key:  accessSecret.Key,
   250  							Path: "auth.yaml",
   251  						},
   252  					},
   253  				},
   254  			},
   255  		})
   256  		volumeMounts = append(volumeMounts, corev1.VolumeMount{
   257  			Name:      "auth-volume",
   258  			MountPath: common.EventBusAuthFileMountPath,
   259  		})
   260  	}
   261  
   262  	// secrets
   263  	volSecrets, volSecretMounts := common.VolumesFromSecretsOrConfigMaps(common.SecretKeySelectorType, secretObjs...)
   264  	volumes = append(volumes, volSecrets...)
   265  	volumeMounts = append(volumeMounts, volSecretMounts...)
   266  
   267  	// config maps
   268  	volConfigMaps, volCofigMapMounts := common.VolumesFromSecretsOrConfigMaps(common.ConfigMapKeySelectorType, eventSourceCopy)
   269  	volumeMounts = append(volumeMounts, volCofigMapMounts...)
   270  	volumes = append(volumes, volConfigMaps...)
   271  
   272  	// Order volumes and volumemounts based on name to make the order deterministic
   273  	sort.Slice(volumes, func(i, j int) bool {
   274  		return volumes[i].Name < volumes[j].Name
   275  	})
   276  	sort.Slice(volumeMounts, func(i, j int) bool {
   277  		return volumeMounts[i].Name < volumeMounts[j].Name
   278  	})
   279  
   280  	deploymentSpec.Template.Spec.Containers[0].Env = append(deploymentSpec.Template.Spec.Containers[0].Env, env...)
   281  	deploymentSpec.Template.Spec.Containers[0].VolumeMounts = append(deploymentSpec.Template.Spec.Containers[0].VolumeMounts, volumeMounts...)
   282  	deploymentSpec.Template.Spec.Volumes = append(deploymentSpec.Template.Spec.Volumes, volumes...)
   283  
   284  	deployment := &appv1.Deployment{
   285  		ObjectMeta: metav1.ObjectMeta{
   286  			Namespace:    args.EventSource.Namespace,
   287  			GenerateName: fmt.Sprintf("%s-eventsource-", args.EventSource.Name),
   288  			Labels:       mergeLabels(args.EventSource.Labels, args.Labels),
   289  		},
   290  		Spec: *deploymentSpec,
   291  	}
   292  	if err := controllerscommon.SetObjectMeta(args.EventSource, deployment, v1alpha1.SchemaGroupVersionKind); err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	return deployment, nil
   297  }
   298  
   299  func buildDeploymentSpec(args *AdaptorArgs) (*appv1.DeploymentSpec, error) {
   300  	eventSourceContainer := corev1.Container{
   301  		Image:           args.Image,
   302  		ImagePullPolicy: common.GetImagePullPolicy(),
   303  		Args:            []string{"eventsource-service"},
   304  		Ports: []corev1.ContainerPort{
   305  			{Name: "metrics", ContainerPort: common.EventSourceMetricsPort},
   306  		},
   307  	}
   308  	if args.EventSource.Spec.Template != nil && args.EventSource.Spec.Template.Container != nil {
   309  		if err := mergo.Merge(&eventSourceContainer, args.EventSource.Spec.Template.Container, mergo.WithOverride); err != nil {
   310  			return nil, err
   311  		}
   312  	}
   313  	eventSourceContainer.Name = "main"
   314  	podTemplateLabels := make(map[string]string)
   315  	if args.EventSource.Spec.Template != nil && args.EventSource.Spec.Template.Metadata != nil &&
   316  		len(args.EventSource.Spec.Template.Metadata.Labels) > 0 {
   317  		for k, v := range args.EventSource.Spec.Template.Metadata.Labels {
   318  			podTemplateLabels[k] = v
   319  		}
   320  	}
   321  	for k, v := range args.Labels {
   322  		podTemplateLabels[k] = v
   323  	}
   324  
   325  	replicas := args.EventSource.Spec.GetReplicas()
   326  	spec := &appv1.DeploymentSpec{
   327  		Selector: &metav1.LabelSelector{
   328  			MatchLabels: args.Labels,
   329  		},
   330  		Replicas: &replicas,
   331  		Template: corev1.PodTemplateSpec{
   332  			ObjectMeta: metav1.ObjectMeta{
   333  				Labels: podTemplateLabels,
   334  			},
   335  			Spec: corev1.PodSpec{
   336  				Containers: []corev1.Container{
   337  					eventSourceContainer,
   338  				},
   339  			},
   340  		},
   341  	}
   342  	if args.EventSource.Spec.Template != nil {
   343  		if args.EventSource.Spec.Template.Metadata != nil {
   344  			spec.Template.SetAnnotations(args.EventSource.Spec.Template.Metadata.Annotations)
   345  		}
   346  		spec.Template.Spec.ServiceAccountName = args.EventSource.Spec.Template.ServiceAccountName
   347  		spec.Template.Spec.Volumes = args.EventSource.Spec.Template.Volumes
   348  		spec.Template.Spec.SecurityContext = args.EventSource.Spec.Template.SecurityContext
   349  		spec.Template.Spec.NodeSelector = args.EventSource.Spec.Template.NodeSelector
   350  		spec.Template.Spec.Tolerations = args.EventSource.Spec.Template.Tolerations
   351  		spec.Template.Spec.Affinity = args.EventSource.Spec.Template.Affinity
   352  		spec.Template.Spec.ImagePullSecrets = args.EventSource.Spec.Template.ImagePullSecrets
   353  		spec.Template.Spec.PriorityClassName = args.EventSource.Spec.Template.PriorityClassName
   354  		spec.Template.Spec.Priority = args.EventSource.Spec.Template.Priority
   355  	}
   356  	return spec, nil
   357  }
   358  
   359  func getService(ctx context.Context, cl client.Client, args *AdaptorArgs) (*corev1.Service, error) {
   360  	sl := &corev1.ServiceList{}
   361  	err := cl.List(ctx, sl, &client.ListOptions{
   362  		Namespace:     args.EventSource.Namespace,
   363  		LabelSelector: labelSelector(args.Labels),
   364  	})
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  	for _, svc := range sl.Items {
   369  		if metav1.IsControlledBy(&svc, args.EventSource) {
   370  			return &svc, nil
   371  		}
   372  	}
   373  	return nil, apierrors.NewNotFound(schema.GroupResource{}, "")
   374  }
   375  
   376  func buildService(args *AdaptorArgs) (*corev1.Service, error) {
   377  	eventSource := args.EventSource
   378  	if eventSource.Spec.Service == nil {
   379  		return nil, nil
   380  	}
   381  	if len(eventSource.Spec.Service.Ports) == 0 {
   382  		return nil, nil
   383  	}
   384  	// Use a ports copy otherwise it will update the oririnal Ports spec in EventSource
   385  	ports := []corev1.ServicePort{}
   386  	ports = append(ports, eventSource.Spec.Service.Ports...)
   387  	svc := &corev1.Service{
   388  		ObjectMeta: metav1.ObjectMeta{
   389  			Name:      fmt.Sprintf("%s-eventsource-svc", eventSource.Name),
   390  			Namespace: eventSource.Namespace,
   391  			Labels:    mergeLabels(args.EventSource.Labels, args.Labels),
   392  		},
   393  		Spec: corev1.ServiceSpec{
   394  			Ports:     ports,
   395  			Type:      corev1.ServiceTypeClusterIP,
   396  			ClusterIP: eventSource.Spec.Service.ClusterIP,
   397  			Selector:  args.Labels,
   398  		},
   399  	}
   400  	if err := controllerscommon.SetObjectMeta(eventSource, svc, v1alpha1.SchemaGroupVersionKind); err != nil {
   401  		return nil, err
   402  	}
   403  	return svc, nil
   404  }
   405  
   406  func mergeLabels(eventBusLabels, given map[string]string) map[string]string {
   407  	result := map[string]string{}
   408  	for k, v := range eventBusLabels {
   409  		result[k] = v
   410  	}
   411  	for k, v := range given {
   412  		result[k] = v
   413  	}
   414  	return result
   415  }
   416  
   417  func labelSelector(labelMap map[string]string) labels.Selector {
   418  	return labels.SelectorFromSet(labelMap)
   419  }