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 }