github.com/argoproj/argo-events@v1.9.1/eventsources/sources/resource/start.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 resource 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "os" 24 "regexp" 25 "strings" 26 "time" 27 28 "github.com/tidwall/gjson" 29 "go.uber.org/zap" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 32 "k8s.io/apimachinery/pkg/labels" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/apimachinery/pkg/selection" 35 "k8s.io/client-go/dynamic" 36 "k8s.io/client-go/dynamic/dynamicinformer" 37 "k8s.io/client-go/tools/cache" 38 39 "github.com/argoproj/argo-events/common" 40 "github.com/argoproj/argo-events/common/logging" 41 eventsourcecommon "github.com/argoproj/argo-events/eventsources/common" 42 "github.com/argoproj/argo-events/eventsources/sources" 43 metrics "github.com/argoproj/argo-events/metrics" 44 apicommon "github.com/argoproj/argo-events/pkg/apis/common" 45 "github.com/argoproj/argo-events/pkg/apis/events" 46 "github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1" 47 ) 48 49 // InformerEvent holds event generated from resource state change 50 type InformerEvent struct { 51 Obj interface{} 52 OldObj interface{} 53 Type v1alpha1.ResourceEventType 54 } 55 56 // EventListener implements Eventing 57 type EventListener struct { 58 EventSourceName string 59 EventName string 60 ResourceEventSource v1alpha1.ResourceEventSource 61 Metrics *metrics.Metrics 62 } 63 64 // GetEventSourceName returns name of event source 65 func (el *EventListener) GetEventSourceName() string { 66 return el.EventSourceName 67 } 68 69 // GetEventName returns name of event 70 func (el *EventListener) GetEventName() string { 71 return el.EventName 72 } 73 74 // GetEventSourceType return type of event server 75 func (el *EventListener) GetEventSourceType() apicommon.EventSourceType { 76 return apicommon.ResourceEvent 77 } 78 79 // StartListening watches resource updates and consume those events 80 func (el *EventListener) StartListening(ctx context.Context, dispatch func([]byte, ...eventsourcecommon.Option) error) error { 81 log := logging.FromContext(ctx). 82 With(logging.LabelEventSourceType, el.GetEventSourceType(), logging.LabelEventName, el.GetEventName()) 83 defer sources.Recover(el.GetEventName()) 84 85 log.Info("setting up a K8s client") 86 kubeConfig, _ := os.LookupEnv(common.EnvVarKubeConfig) 87 restConfig, err := common.GetClientConfig(kubeConfig) 88 if err != nil { 89 return fmt.Errorf("failed to get a K8s rest config for the event source %s, %w", el.GetEventName(), err) 90 } 91 client, err := dynamic.NewForConfig(restConfig) 92 if err != nil { 93 return fmt.Errorf("failed to set up a dynamic K8s client for the event source %s, %w", el.GetEventName(), err) 94 } 95 96 resourceEventSource := &el.ResourceEventSource 97 98 gvr := schema.GroupVersionResource{ 99 Group: resourceEventSource.Group, 100 Version: resourceEventSource.Version, 101 Resource: resourceEventSource.Resource, 102 } 103 104 client.Resource(gvr) 105 106 options := &metav1.ListOptions{} 107 108 log.Info("configuring label selectors if filters are selected...") 109 if resourceEventSource.Filter != nil && resourceEventSource.Filter.Labels != nil { 110 sel, err := LabelSelector(resourceEventSource.Filter.Labels) 111 if err != nil { 112 return fmt.Errorf("failed to create the label selector for the event source %s, %w", el.GetEventName(), err) 113 } 114 options.LabelSelector = sel.String() 115 } 116 117 tweakListOptions := func(op *metav1.ListOptions) { 118 *op = *options 119 } 120 121 log.Info("setting up informer factory...") 122 factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(client, 0, resourceEventSource.Namespace, tweakListOptions) 123 124 informer := factory.ForResource(gvr) 125 126 informerEventCh := make(chan *InformerEvent) 127 stopCh := make(chan struct{}) 128 startTime := time.Now() 129 130 processOne := func(event *InformerEvent) error { 131 if !passFilters(event, resourceEventSource.Filter, startTime, log) { 132 return nil 133 } 134 135 defer func(start time.Time) { 136 el.Metrics.EventProcessingDuration(el.GetEventSourceName(), el.GetEventName(), float64(time.Since(start)/time.Millisecond)) 137 }(time.Now()) 138 139 objBody, err := json.Marshal(event.Obj) 140 if err != nil { 141 return fmt.Errorf("failed to marshal the resource, rejecting the event, %w", err) 142 } 143 144 var oldObjBody []byte 145 if event.OldObj != nil { 146 oldObjBody, err = json.Marshal(event.OldObj) 147 if err != nil { 148 return fmt.Errorf("failed to marshal the resource, rejecting the event, %w", err) 149 } 150 } 151 152 eventData := &events.ResourceEventData{ 153 EventType: string(event.Type), 154 Body: (*json.RawMessage)(&objBody), 155 OldBody: (*json.RawMessage)(&oldObjBody), 156 Group: resourceEventSource.Group, 157 Version: resourceEventSource.Version, 158 Resource: resourceEventSource.Resource, 159 Metadata: resourceEventSource.Metadata, 160 } 161 162 eventBody, err := json.Marshal(eventData) 163 if err != nil { 164 return fmt.Errorf("failed to marshal the event. rejecting the event, %w", err) 165 } 166 167 if err = dispatch(eventBody); err != nil { 168 return fmt.Errorf("failed to dispatch a resource event, %w", err) 169 } 170 return nil 171 } 172 173 go func() { 174 log.Info("listening to resource events...") 175 for { 176 select { 177 case event := <-informerEventCh: 178 if err := processOne(event); err != nil { 179 log.Errorw("failed to process a Resource event", zap.Error(err)) 180 el.Metrics.EventProcessingFailed(el.GetEventSourceName(), el.GetEventName()) 181 } 182 case <-stopCh: 183 return 184 } 185 } 186 }() 187 188 handlerFuncs := cache.ResourceEventHandlerFuncs{} 189 190 for _, eventType := range resourceEventSource.EventTypes { 191 switch eventType { 192 case v1alpha1.ADD: 193 handlerFuncs.AddFunc = func(obj interface{}) { 194 log.Info("detected create event") 195 informerEventCh <- &InformerEvent{ 196 Obj: obj, 197 Type: v1alpha1.ADD, 198 } 199 } 200 case v1alpha1.UPDATE: 201 handlerFuncs.UpdateFunc = func(oldObj, newObj interface{}) { 202 log.Info("detected update event") 203 uNewObj := newObj.(*unstructured.Unstructured) 204 uOldObj := oldObj.(*unstructured.Unstructured) 205 if uNewObj.GetResourceVersion() == uOldObj.GetResourceVersion() { 206 log.Infof("rejecting update event with identical resource versions: %s", uNewObj.GetResourceVersion()) 207 return 208 } 209 informerEventCh <- &InformerEvent{ 210 Obj: newObj, 211 OldObj: oldObj, 212 Type: v1alpha1.UPDATE, 213 } 214 } 215 case v1alpha1.DELETE: 216 handlerFuncs.DeleteFunc = func(obj interface{}) { 217 log.Info("detected delete event") 218 informerEventCh <- &InformerEvent{ 219 Obj: obj, 220 Type: v1alpha1.DELETE, 221 } 222 } 223 default: 224 stopCh <- struct{}{} 225 return fmt.Errorf("unknown event type: %s", string(eventType)) 226 } 227 } 228 229 sharedInformer := informer.Informer() 230 sharedInformer.AddEventHandler(handlerFuncs) 231 232 doneCh := make(chan struct{}) 233 234 log.Info("running informer...") 235 sharedInformer.Run(doneCh) 236 237 <-ctx.Done() 238 doneCh <- struct{}{} 239 stopCh <- struct{}{} 240 241 log.Info("event source is stopped") 242 close(informerEventCh) 243 244 return nil 245 } 246 247 // LabelReq returns label requirements 248 func LabelReq(sel v1alpha1.Selector) (*labels.Requirement, error) { 249 op := selection.Equals 250 if sel.Operation != "" { 251 op = selection.Operator(sel.Operation) 252 } 253 var values []string 254 switch { 255 case (op == selection.Exists || op == selection.DoesNotExist) && sel.Value == "": 256 values = []string{} 257 case op == selection.In || op == selection.NotIn: 258 values = strings.Split(sel.Value, ",") 259 default: 260 values = []string{sel.Value} 261 } 262 req, err := labels.NewRequirement(sel.Key, op, values) 263 if err != nil { 264 return nil, err 265 } 266 return req, nil 267 } 268 269 // LabelSelector returns label selector for resource filtering 270 func LabelSelector(selectors []v1alpha1.Selector) (labels.Selector, error) { 271 var labelRequirements []labels.Requirement 272 for _, sel := range selectors { 273 req, err := LabelReq(sel) 274 if err != nil { 275 return nil, err 276 } 277 labelRequirements = append(labelRequirements, *req) 278 } 279 return labels.NewSelector().Add(labelRequirements...), nil 280 } 281 282 // helper method to check if the object passed the user defined filters 283 func passFilters(event *InformerEvent, filter *v1alpha1.ResourceFilter, startTime time.Time, log *zap.SugaredLogger) bool { 284 // no filters are applied. 285 if filter == nil { 286 return true 287 } 288 uObj := event.Obj.(*unstructured.Unstructured) 289 if len(filter.Prefix) > 0 && !strings.HasPrefix(uObj.GetName(), filter.Prefix) { 290 log.Infof("resource name does not match prefix. resource-name: %s, prefix: %s\n", uObj.GetName(), filter.Prefix) 291 return false 292 } 293 eventTime := getEventTime(uObj, event.Type) 294 if filter.AfterStart && eventTime.UTC().Before(startTime.UTC()) { 295 log.Infof("Event happened before service start time. event-timestamp: %s, start-timestamp: %s\n", eventTime.UTC().String(), startTime.UTC().String()) 296 return false 297 } 298 created := uObj.GetCreationTimestamp() 299 if !filter.CreatedBy.IsZero() && created.UTC().After(filter.CreatedBy.UTC()) { 300 log.Infof("resource is created after filter time. creation-timestamp: %s, filter-creation-timestamp: %s\n", created.UTC().String(), filter.CreatedBy.UTC().String()) 301 return false 302 } 303 if len(filter.Fields) > 0 { 304 jsData, err := uObj.MarshalJSON() 305 if err != nil { 306 log.Errorw("failed to marshal informer event", zap.Error(err)) 307 return false 308 } 309 310 return filterFields(jsData, filter.Fields, log) 311 } 312 return true 313 } 314 315 func filterFields(jsonData []byte, selectors []v1alpha1.Selector, log *zap.SugaredLogger) bool { 316 for _, selector := range selectors { 317 res := gjson.GetBytes(jsonData, selector.Key) 318 if !res.Exists() { 319 return false 320 } 321 exp, err := regexp.Compile(selector.Value) 322 if err != nil { 323 log.Errorw("invalid regex", zap.Error(err)) 324 return false 325 } 326 match := exp.Match([]byte(res.Str)) 327 328 switch selection.Operator(selector.Operation) { 329 case selection.Equals, selection.DoubleEquals: 330 if !match { 331 return false 332 } 333 case selection.NotEquals: 334 if match { 335 return false 336 } 337 default: 338 log.Errorf("invalid operator, only %v, %v and %v are supported", selection.Equals, selection.DoubleEquals, selection.NotEquals) 339 return false 340 } 341 } 342 return true 343 } 344 345 func getEventTime(obj *unstructured.Unstructured, eventType v1alpha1.ResourceEventType) metav1.Time { 346 switch eventType { 347 case v1alpha1.ADD: 348 return obj.GetCreationTimestamp() 349 case v1alpha1.DELETE: 350 if obj.GetDeletionTimestamp() != nil { 351 return *obj.GetDeletionTimestamp() 352 } else { 353 return metav1.Now() 354 } 355 case v1alpha1.UPDATE: 356 t := obj.GetCreationTimestamp() 357 for _, f := range obj.GetManagedFields() { 358 if f.Operation == metav1.ManagedFieldsOperationUpdate && f.Time.UTC().After(t.UTC()) { 359 t = *f.Time 360 } 361 } 362 return t 363 default: 364 return obj.GetCreationTimestamp() 365 } 366 }