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 }