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

     1  /*
     2  Copyright 2018 BlackRock, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package sensor
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"encoding/json"
    23  	"fmt"
    24  	"sort"
    25  
    26  	"github.com/imdario/mergo"
    27  	"go.uber.org/zap"
    28  	appv1 "k8s.io/api/apps/v1"
    29  	corev1 "k8s.io/api/core/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/labels"
    33  	"k8s.io/apimachinery/pkg/runtime/schema"
    34  	"sigs.k8s.io/controller-runtime/pkg/client"
    35  
    36  	"github.com/argoproj/argo-events/common"
    37  	controllerscommon "github.com/argoproj/argo-events/controllers/common"
    38  	eventbusv1alpha1 "github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1"
    39  	"github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1"
    40  )
    41  
    42  // AdaptorArgs are the args needed to create a sensor deployment
    43  type AdaptorArgs struct {
    44  	Image  string
    45  	Sensor *v1alpha1.Sensor
    46  	Labels map[string]string
    47  }
    48  
    49  // Reconcile does the real logic
    50  func Reconcile(client client.Client, eventBus *eventbusv1alpha1.EventBus, args *AdaptorArgs, logger *zap.SugaredLogger) error {
    51  	ctx := context.Background()
    52  	sensor := args.Sensor
    53  
    54  	if eventBus == nil {
    55  		sensor.Status.MarkDeployFailed("GetEventBusFailed", "Failed to get EventBus.")
    56  		logger.Error("failed to get EventBus")
    57  		return fmt.Errorf("failed to get EventBus")
    58  	}
    59  
    60  	eventBusName := common.DefaultEventBusName
    61  	if len(sensor.Spec.EventBusName) > 0 {
    62  		eventBusName = sensor.Spec.EventBusName
    63  	}
    64  	if !eventBus.Status.IsReady() {
    65  		sensor.Status.MarkDeployFailed("EventBusNotReady", "EventBus not ready.")
    66  		logger.Errorw("event bus is not in ready status", "eventBusName", eventBusName)
    67  		return fmt.Errorf("eventbus not ready")
    68  	}
    69  
    70  	expectedDeploy, err := buildDeployment(args, eventBus)
    71  	if err != nil {
    72  		sensor.Status.MarkDeployFailed("BuildDeploymentSpecFailed", "Failed to build Deployment spec.")
    73  		logger.Errorw("failed to build deployment spec", "error", err)
    74  		return err
    75  	}
    76  	deploy, err := getDeployment(ctx, client, args)
    77  	if err != nil && !apierrors.IsNotFound(err) {
    78  		sensor.Status.MarkDeployFailed("GetDeploymentFailed", "Get existing deployment failed")
    79  		logger.Errorw("error getting existing deployment", "error", err)
    80  		return err
    81  	}
    82  	if deploy != nil {
    83  		if deploy.Annotations != nil && deploy.Annotations[common.AnnotationResourceSpecHash] != expectedDeploy.Annotations[common.AnnotationResourceSpecHash] {
    84  			deploy.Spec = expectedDeploy.Spec
    85  			deploy.SetLabels(expectedDeploy.Labels)
    86  			deploy.Annotations[common.AnnotationResourceSpecHash] = expectedDeploy.Annotations[common.AnnotationResourceSpecHash]
    87  			err = client.Update(ctx, deploy)
    88  			if err != nil {
    89  				sensor.Status.MarkDeployFailed("UpdateDeploymentFailed", "Failed to update existing deployment")
    90  				logger.Errorw("error updating existing deployment", "error", err)
    91  				return err
    92  			}
    93  			logger.Infow("deployment is updated", "deploymentName", deploy.Name)
    94  		}
    95  	} else {
    96  		err = client.Create(ctx, expectedDeploy)
    97  		if err != nil {
    98  			sensor.Status.MarkDeployFailed("CreateDeploymentFailed", "Failed to create a deployment")
    99  			logger.Errorw("error creating a deployment", "error", err)
   100  			return err
   101  		}
   102  		logger.Infow("deployment is created", "deploymentName", expectedDeploy.Name)
   103  	}
   104  	sensor.Status.MarkDeployed()
   105  	return nil
   106  }
   107  
   108  func getDeployment(ctx context.Context, cl client.Client, args *AdaptorArgs) (*appv1.Deployment, error) {
   109  	dl := &appv1.DeploymentList{}
   110  	err := cl.List(ctx, dl, &client.ListOptions{
   111  		Namespace:     args.Sensor.Namespace,
   112  		LabelSelector: labelSelector(args.Labels),
   113  	})
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	for _, deploy := range dl.Items {
   118  		if metav1.IsControlledBy(&deploy, args.Sensor) {
   119  			return &deploy, nil
   120  		}
   121  	}
   122  	return nil, apierrors.NewNotFound(schema.GroupResource{}, "")
   123  }
   124  
   125  func buildDeployment(args *AdaptorArgs, eventBus *eventbusv1alpha1.EventBus) (*appv1.Deployment, error) {
   126  	deploymentSpec, err := buildDeploymentSpec(args)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	sensorCopy := &v1alpha1.Sensor{
   131  		ObjectMeta: metav1.ObjectMeta{
   132  			Namespace: args.Sensor.Namespace,
   133  			Name:      args.Sensor.Name,
   134  		},
   135  		Spec: args.Sensor.Spec,
   136  	}
   137  	sensorBytes, err := json.Marshal(sensorCopy)
   138  	if err != nil {
   139  		return nil, fmt.Errorf("failed marshal sensor spec")
   140  	}
   141  	busConfigBytes, err := json.Marshal(eventBus.Status.Config)
   142  	if err != nil {
   143  		return nil, fmt.Errorf("failed marshal event bus config: %v", err)
   144  	}
   145  
   146  	env := []corev1.EnvVar{
   147  		{
   148  			Name:  common.EnvVarSensorObject,
   149  			Value: base64.StdEncoding.EncodeToString(sensorBytes),
   150  		},
   151  		{
   152  			Name:  common.EnvVarEventBusSubject,
   153  			Value: fmt.Sprintf("eventbus-%s", args.Sensor.Namespace),
   154  		},
   155  		{
   156  			Name:      common.EnvVarPodName,
   157  			ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}},
   158  		},
   159  		{
   160  			Name:  common.EnvVarLeaderElection,
   161  			Value: args.Sensor.Annotations[common.AnnotationLeaderElection],
   162  		},
   163  		{
   164  			Name:  common.EnvVarEventBusConfig,
   165  			Value: base64.StdEncoding.EncodeToString(busConfigBytes),
   166  		},
   167  	}
   168  
   169  	volumes := []corev1.Volume{
   170  		{
   171  			Name:         "tmp",
   172  			VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
   173  		},
   174  	}
   175  
   176  	volumeMounts := []corev1.VolumeMount{
   177  		{
   178  			Name:      "tmp",
   179  			MountPath: "/tmp",
   180  		},
   181  	}
   182  
   183  	var secretObjs []interface{}
   184  	var accessSecret *corev1.SecretKeySelector
   185  	switch {
   186  	case eventBus.Status.Config.NATS != nil:
   187  		accessSecret = eventBus.Status.Config.NATS.AccessSecret
   188  		secretObjs = []interface{}{sensorCopy}
   189  	case eventBus.Status.Config.JetStream != nil:
   190  		accessSecret = eventBus.Status.Config.JetStream.AccessSecret
   191  		secretObjs = []interface{}{sensorCopy}
   192  	case eventBus.Status.Config.Kafka != nil:
   193  		accessSecret = nil
   194  		secretObjs = []interface{}{sensorCopy, eventBus} // kafka requires secrets for sasl and tls
   195  	default:
   196  		return nil, fmt.Errorf("unsupported event bus")
   197  	}
   198  
   199  	if accessSecret != nil {
   200  		// Mount the secret as volume instead of using envFrom to gain the ability
   201  		// for the sensor deployment to auto reload when the secret changes
   202  		volumes = append(volumes, corev1.Volume{
   203  			Name: "auth-volume",
   204  			VolumeSource: corev1.VolumeSource{
   205  				Secret: &corev1.SecretVolumeSource{
   206  					SecretName: accessSecret.Name,
   207  					Items: []corev1.KeyToPath{
   208  						{
   209  							Key:  accessSecret.Key,
   210  							Path: "auth.yaml",
   211  						},
   212  					},
   213  				},
   214  			},
   215  		})
   216  		volumeMounts = append(volumeMounts, corev1.VolumeMount{
   217  			Name:      "auth-volume",
   218  			MountPath: common.EventBusAuthFileMountPath,
   219  		})
   220  	}
   221  
   222  	// secrets
   223  	volSecrets, volSecretMounts := common.VolumesFromSecretsOrConfigMaps(common.SecretKeySelectorType, secretObjs...)
   224  	volumes = append(volumes, volSecrets...)
   225  	volumeMounts = append(volumeMounts, volSecretMounts...)
   226  
   227  	// config maps
   228  	volConfigMaps, volCofigMapMounts := common.VolumesFromSecretsOrConfigMaps(common.ConfigMapKeySelectorType, sensorCopy)
   229  	volumeMounts = append(volumeMounts, volCofigMapMounts...)
   230  	volumes = append(volumes, volConfigMaps...)
   231  
   232  	// Order volumes and volumemounts based on name to make the order deterministic
   233  	sort.Slice(volumes, func(i, j int) bool {
   234  		return volumes[i].Name < volumes[j].Name
   235  	})
   236  	sort.Slice(volumeMounts, func(i, j int) bool {
   237  		return volumeMounts[i].Name < volumeMounts[j].Name
   238  	})
   239  
   240  	deploymentSpec.Template.Spec.Containers[0].Env = append(deploymentSpec.Template.Spec.Containers[0].Env, env...)
   241  	deploymentSpec.Template.Spec.Containers[0].VolumeMounts = append(deploymentSpec.Template.Spec.Containers[0].VolumeMounts, volumeMounts...)
   242  	deploymentSpec.Template.Spec.Volumes = append(deploymentSpec.Template.Spec.Volumes, volumes...)
   243  
   244  	deployment := &appv1.Deployment{
   245  		ObjectMeta: metav1.ObjectMeta{
   246  			Namespace:    args.Sensor.Namespace,
   247  			GenerateName: fmt.Sprintf("%s-sensor-", args.Sensor.Name),
   248  			Labels:       mergeLabels(args.Sensor.Labels, args.Labels),
   249  		},
   250  		Spec: *deploymentSpec,
   251  	}
   252  	if err := controllerscommon.SetObjectMeta(args.Sensor, deployment, v1alpha1.SchemaGroupVersionKind); err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	return deployment, nil
   257  }
   258  
   259  func buildDeploymentSpec(args *AdaptorArgs) (*appv1.DeploymentSpec, error) {
   260  	replicas := args.Sensor.Spec.GetReplicas()
   261  	sensorContainer := corev1.Container{
   262  		Image:           args.Image,
   263  		ImagePullPolicy: common.GetImagePullPolicy(),
   264  		Args:            []string{"sensor-service"},
   265  		Ports: []corev1.ContainerPort{
   266  			{Name: "metrics", ContainerPort: common.SensorMetricsPort},
   267  		},
   268  	}
   269  	if args.Sensor.Spec.Template != nil && args.Sensor.Spec.Template.Container != nil {
   270  		if err := mergo.Merge(&sensorContainer, args.Sensor.Spec.Template.Container, mergo.WithOverride); err != nil {
   271  			return nil, err
   272  		}
   273  	}
   274  	sensorContainer.Name = "main"
   275  	podTemplateLabels := make(map[string]string)
   276  	if args.Sensor.Spec.Template != nil && args.Sensor.Spec.Template.Metadata != nil &&
   277  		len(args.Sensor.Spec.Template.Metadata.Labels) > 0 {
   278  		for k, v := range args.Sensor.Spec.Template.Metadata.Labels {
   279  			podTemplateLabels[k] = v
   280  		}
   281  	}
   282  	for k, v := range args.Labels {
   283  		podTemplateLabels[k] = v
   284  	}
   285  	spec := &appv1.DeploymentSpec{
   286  		Selector: &metav1.LabelSelector{
   287  			MatchLabels: args.Labels,
   288  		},
   289  		Replicas:             &replicas,
   290  		RevisionHistoryLimit: args.Sensor.Spec.RevisionHistoryLimit,
   291  		Template: corev1.PodTemplateSpec{
   292  			ObjectMeta: metav1.ObjectMeta{
   293  				Labels: podTemplateLabels,
   294  			},
   295  			Spec: corev1.PodSpec{
   296  				Containers: []corev1.Container{
   297  					sensorContainer,
   298  				},
   299  			},
   300  		},
   301  	}
   302  	if args.Sensor.Spec.Template != nil {
   303  		if args.Sensor.Spec.Template.Metadata != nil {
   304  			spec.Template.SetAnnotations(args.Sensor.Spec.Template.Metadata.Annotations)
   305  		}
   306  		spec.Template.Spec.ServiceAccountName = args.Sensor.Spec.Template.ServiceAccountName
   307  		spec.Template.Spec.Volumes = args.Sensor.Spec.Template.Volumes
   308  		spec.Template.Spec.SecurityContext = args.Sensor.Spec.Template.SecurityContext
   309  		spec.Template.Spec.NodeSelector = args.Sensor.Spec.Template.NodeSelector
   310  		spec.Template.Spec.Tolerations = args.Sensor.Spec.Template.Tolerations
   311  		spec.Template.Spec.Affinity = args.Sensor.Spec.Template.Affinity
   312  		spec.Template.Spec.ImagePullSecrets = args.Sensor.Spec.Template.ImagePullSecrets
   313  		spec.Template.Spec.PriorityClassName = args.Sensor.Spec.Template.PriorityClassName
   314  		spec.Template.Spec.Priority = args.Sensor.Spec.Template.Priority
   315  	}
   316  	return spec, nil
   317  }
   318  
   319  func mergeLabels(sensorLabels, given map[string]string) map[string]string {
   320  	result := map[string]string{}
   321  	for k, v := range sensorLabels {
   322  		result[k] = v
   323  	}
   324  	for k, v := range given {
   325  		result[k] = v
   326  	}
   327  	return result
   328  }
   329  
   330  func labelSelector(labelMap map[string]string) labels.Selector {
   331  	return labels.SelectorFromSet(labelMap)
   332  }