github.com/argoproj/argo-events@v1.9.1/controllers/sensor/validate.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 "fmt" 21 "net/http" 22 "time" 23 24 cronlib "github.com/robfig/cron/v3" 25 26 "github.com/argoproj/argo-events/common" 27 eventbusv1alpha1 "github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1" 28 "github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1" 29 ) 30 31 // ValidateSensor accepts a sensor and performs validation against it 32 // we return an error so that it can be logged as a message on the sensor status 33 // the error is ignored by the operation context as subsequent re-queues would produce the same error. 34 // Exporting this function so that external APIs can use this to validate sensor resource. 35 func ValidateSensor(s *v1alpha1.Sensor, b *eventbusv1alpha1.EventBus) error { 36 if s == nil { 37 s.Status.MarkDependenciesNotProvided("InvalidSensor", "nil sensor") 38 return fmt.Errorf("nil sensor") 39 } 40 if b == nil { 41 s.Status.MarkDependenciesNotProvided("InvalidEventBus", "nil eventbus") 42 return fmt.Errorf("nil eventbus") 43 } 44 if err := validateDependencies(s.Spec.Dependencies, b); err != nil { 45 s.Status.MarkDependenciesNotProvided("InvalidDependencies", err.Error()) 46 return err 47 } 48 s.Status.MarkDependenciesProvided() 49 err := validateTriggers(s.Spec.Triggers) 50 if err != nil { 51 s.Status.MarkTriggersNotProvided("InvalidTriggers", err.Error()) 52 return err 53 } 54 s.Status.MarkTriggersProvided() 55 return nil 56 } 57 58 // validateTriggers validates triggers 59 func validateTriggers(triggers []v1alpha1.Trigger) error { 60 if len(triggers) < 1 { 61 return fmt.Errorf("no triggers found") 62 } 63 64 trigNames := make(map[string]bool) 65 66 for _, trigger := range triggers { 67 if err := validateTriggerTemplate(trigger.Template); err != nil { 68 return err 69 } 70 if _, ok := trigNames[trigger.Template.Name]; ok { 71 return fmt.Errorf("duplicate trigger name: %s", trigger.Template.Name) 72 } 73 trigNames[trigger.Template.Name] = true 74 if err := validateTriggerPolicy(&trigger); err != nil { 75 return err 76 } 77 if err := validateTriggerTemplateParameters(&trigger); err != nil { 78 return err 79 } 80 } 81 return nil 82 } 83 84 // validateTriggerTemplate validates trigger template 85 func validateTriggerTemplate(template *v1alpha1.TriggerTemplate) error { 86 if template == nil { 87 return fmt.Errorf("trigger template can't be nil") 88 } 89 if template.Name == "" { 90 return fmt.Errorf("trigger must define a name") 91 } 92 if len(template.ConditionsReset) > 0 { 93 for _, c := range template.ConditionsReset { 94 if c.ByTime == nil { 95 return fmt.Errorf("invalid conditionsReset") 96 } 97 parser := cronlib.NewParser(cronlib.Minute | cronlib.Hour | cronlib.Dom | cronlib.Month | cronlib.Dow) 98 if _, err := parser.Parse(c.ByTime.Cron); err != nil { 99 return fmt.Errorf("invalid cron expression %q", c.ByTime.Cron) 100 } 101 if _, err := time.LoadLocation(c.ByTime.Timezone); err != nil { 102 return fmt.Errorf("invalid timezone %q", c.ByTime.Timezone) 103 } 104 } 105 } 106 if template.K8s != nil { 107 if err := validateK8STrigger(template.K8s); err != nil { 108 return fmt.Errorf("trigger for template %s is invalid, %w", template.Name, err) 109 } 110 } 111 if template.ArgoWorkflow != nil { 112 if err := validateArgoWorkflowTrigger(template.ArgoWorkflow); err != nil { 113 return fmt.Errorf("template %s is invalid, %w", template.Name, err) 114 } 115 } 116 if template.HTTP != nil { 117 if err := validateHTTPTrigger(template.HTTP); err != nil { 118 return fmt.Errorf("template %s is invalid, %w", template.Name, err) 119 } 120 } 121 if template.AWSLambda != nil { 122 if err := validateAWSLambdaTrigger(template.AWSLambda); err != nil { 123 return fmt.Errorf("template %s is invalid, %w", template.Name, err) 124 } 125 } 126 if template.Kafka != nil { 127 if err := validateKafkaTrigger(template.Kafka); err != nil { 128 return fmt.Errorf("template %s is invalid, %w", template.Name, err) 129 } 130 } 131 if template.NATS != nil { 132 if err := validateNATSTrigger(template.NATS); err != nil { 133 return fmt.Errorf("template %s is invalid, %w", template.Name, err) 134 } 135 } 136 if template.Slack != nil { 137 if err := validateSlackTrigger(template.Slack); err != nil { 138 return fmt.Errorf("template %s is invalid, %w", template.Name, err) 139 } 140 } 141 if template.OpenWhisk != nil { 142 if err := validateOpenWhiskTrigger(template.OpenWhisk); err != nil { 143 return fmt.Errorf("template %s is invalid, %w", template.Name, err) 144 } 145 } 146 if template.CustomTrigger != nil { 147 if err := validateCustomTrigger(template.CustomTrigger); err != nil { 148 return fmt.Errorf("template %s is invalid, %w", template.Name, err) 149 } 150 } 151 if template.Email != nil { 152 if err := validateEmailTrigger(template.Email); err != nil { 153 return fmt.Errorf("template %s is invalid, %w", template.Name, err) 154 } 155 } 156 return nil 157 } 158 159 // validateK8STrigger validates a kubernetes trigger 160 func validateK8STrigger(trigger *v1alpha1.StandardK8STrigger) error { 161 if trigger == nil { 162 return fmt.Errorf("k8s trigger can't be nil") 163 } 164 if trigger.Source == nil { 165 return fmt.Errorf("k8s trigger does not contain an absolute action") 166 } 167 168 switch trigger.Operation { 169 case "", v1alpha1.Create, v1alpha1.Patch, v1alpha1.Update, v1alpha1.Delete: 170 171 default: 172 return fmt.Errorf("unknown operation type %s", string(trigger.Operation)) 173 } 174 if trigger.Parameters != nil { 175 for i, parameter := range trigger.Parameters { 176 if err := validateTriggerParameter(¶meter); err != nil { 177 return fmt.Errorf("resource parameter index: %d. err: %w", i, err) 178 } 179 } 180 } 181 return nil 182 } 183 184 // validateArgoWorkflowTrigger validates an Argo workflow trigger 185 func validateArgoWorkflowTrigger(trigger *v1alpha1.ArgoWorkflowTrigger) error { 186 if trigger == nil { 187 return fmt.Errorf("argoWorkflow trigger can't be nil") 188 } 189 if trigger.Source == nil { 190 return fmt.Errorf("argoWorkflow trigger does not contain an absolute action") 191 } 192 193 switch trigger.Operation { 194 case v1alpha1.Submit, v1alpha1.SubmitFrom, v1alpha1.Suspend, v1alpha1.Retry, v1alpha1.Resume, v1alpha1.Resubmit, v1alpha1.Terminate, v1alpha1.Stop: 195 default: 196 return fmt.Errorf("unknown operation type %s", string(trigger.Operation)) 197 } 198 if trigger.Parameters != nil { 199 for i, parameter := range trigger.Parameters { 200 if err := validateTriggerParameter(¶meter); err != nil { 201 return fmt.Errorf("resource parameter index: %d. err: %w", i, err) 202 } 203 } 204 } 205 return nil 206 } 207 208 // validateHTTPTrigger validates the HTTP trigger 209 func validateHTTPTrigger(trigger *v1alpha1.HTTPTrigger) error { 210 if trigger == nil { 211 return fmt.Errorf("HTTP trigger for can't be nil") 212 } 213 if trigger.URL == "" { 214 return fmt.Errorf("server URL is not specified") 215 } 216 if trigger.Method != "" { 217 switch trigger.Method { 218 case http.MethodGet, http.MethodDelete, http.MethodPatch, http.MethodPost, http.MethodPut: 219 default: 220 return fmt.Errorf("only GET, DELETE, PATCH, POST and PUT methods are supported") 221 } 222 } 223 if trigger.Parameters != nil { 224 for i, parameter := range trigger.Parameters { 225 if err := validateTriggerParameter(¶meter); err != nil { 226 return fmt.Errorf("resource parameter index: %d. err: %w", i, err) 227 } 228 } 229 } 230 if trigger.Payload != nil { 231 for i, p := range trigger.Payload { 232 if err := validateTriggerParameter(&p); err != nil { 233 return fmt.Errorf("payload index: %d. err: %w", i, err) 234 } 235 } 236 } 237 return nil 238 } 239 240 // validateOpenWhiskTrigger validates the OpenWhisk trigger 241 func validateOpenWhiskTrigger(trigger *v1alpha1.OpenWhiskTrigger) error { 242 if trigger == nil { 243 return fmt.Errorf("openwhisk trigger for can't be nil") 244 } 245 if trigger.ActionName == "" { 246 return fmt.Errorf("action name is not specified") 247 } 248 if trigger.Host == "" { 249 return fmt.Errorf("host URL is not specified") 250 } 251 if trigger.AuthToken != nil { 252 if trigger.AuthToken.Name == "" || trigger.AuthToken.Key == "" { 253 return fmt.Errorf("auth token key and name must be specified") 254 } 255 } 256 if trigger.Parameters != nil { 257 for i, parameter := range trigger.Parameters { 258 if err := validateTriggerParameter(¶meter); err != nil { 259 return fmt.Errorf("resource parameter index: %d. err: %w", i, err) 260 } 261 } 262 } 263 if trigger.Payload == nil { 264 return fmt.Errorf("payload parameters are not specified") 265 } 266 if trigger.Payload != nil { 267 for i, p := range trigger.Payload { 268 if err := validateTriggerParameter(&p); err != nil { 269 return fmt.Errorf("resource parameter index: %d. err: %w", i, err) 270 } 271 } 272 } 273 return nil 274 } 275 276 // validateAWSLambdaTrigger validates the AWS Lambda trigger 277 func validateAWSLambdaTrigger(trigger *v1alpha1.AWSLambdaTrigger) error { 278 if trigger == nil { 279 return fmt.Errorf("openfaas trigger for can't be nil") 280 } 281 if trigger.FunctionName == "" { 282 return fmt.Errorf("function name is not specified") 283 } 284 if trigger.Region == "" { 285 return fmt.Errorf("region in not specified") 286 } 287 if trigger.Payload == nil { 288 return fmt.Errorf("payload parameters are not specified") 289 } 290 if trigger.Parameters != nil { 291 for i, parameter := range trigger.Parameters { 292 if err := validateTriggerParameter(¶meter); err != nil { 293 return fmt.Errorf("resource parameter index: %d. err: %w", i, err) 294 } 295 } 296 } 297 if trigger.Payload != nil { 298 for i, p := range trigger.Payload { 299 if err := validateTriggerParameter(&p); err != nil { 300 return fmt.Errorf("payload index: %d. err: %w", i, err) 301 } 302 } 303 } 304 return nil 305 } 306 307 // validateKafkaTrigger validates the kafka trigger. 308 func validateKafkaTrigger(trigger *v1alpha1.KafkaTrigger) error { 309 if trigger == nil { 310 return fmt.Errorf("trigger can't be nil") 311 } 312 if trigger.URL == "" { 313 return fmt.Errorf("broker url must not be empty") 314 } 315 if trigger.Payload == nil { 316 return fmt.Errorf("payload must not be empty") 317 } 318 if trigger.Topic == "" { 319 return fmt.Errorf("topic must not be empty") 320 } 321 if trigger.Payload != nil { 322 for i, p := range trigger.Payload { 323 if err := validateTriggerParameter(&p); err != nil { 324 return fmt.Errorf("payload index: %d. err: %w", i, err) 325 } 326 } 327 } 328 return nil 329 } 330 331 // validateNATSTrigger validates the NATS trigger. 332 func validateNATSTrigger(trigger *v1alpha1.NATSTrigger) error { 333 if trigger == nil { 334 return fmt.Errorf("trigger can't be nil") 335 } 336 if trigger.URL == "" { 337 return fmt.Errorf("nats server url can't be empty") 338 } 339 if trigger.Subject == "" { 340 return fmt.Errorf("nats subject can't be empty") 341 } 342 if trigger.Payload == nil { 343 return fmt.Errorf("payload can't be nil") 344 } 345 if trigger.Payload != nil { 346 for i, p := range trigger.Payload { 347 if err := validateTriggerParameter(&p); err != nil { 348 return fmt.Errorf("payload index: %d. err: %w", i, err) 349 } 350 } 351 } 352 return nil 353 } 354 355 // validateSlackTrigger validates the Slack trigger. 356 func validateSlackTrigger(trigger *v1alpha1.SlackTrigger) error { 357 if trigger == nil { 358 return fmt.Errorf("trigger can't be nil") 359 } 360 if trigger.SlackToken == nil { 361 return fmt.Errorf("slack token can't be empty") 362 } 363 if trigger.Parameters != nil { 364 for i, parameter := range trigger.Parameters { 365 if err := validateTriggerParameter(¶meter); err != nil { 366 return fmt.Errorf("resource parameter index: %d. err: %w", i, err) 367 } 368 } 369 } 370 return nil 371 } 372 373 // validateEmailTrigger validates the Email trigger 374 func validateEmailTrigger(trigger *v1alpha1.EmailTrigger) error { 375 if trigger == nil { 376 return fmt.Errorf("trigger can't be nil") 377 } 378 if trigger.Host == "" { 379 return fmt.Errorf("host can't be empty") 380 } 381 if 0 > trigger.Port || trigger.Port > 65535 { 382 return fmt.Errorf("port: %v, port should be between 0-65535", trigger.Port) 383 } 384 if trigger.Parameters != nil { 385 for i, parameter := range trigger.Parameters { 386 if err := validateTriggerParameter(¶meter); err != nil { 387 return fmt.Errorf("resource parameter index: %d. err: %w", i, err) 388 } 389 } 390 } 391 return nil 392 } 393 394 // validateCustomTrigger validates the custom trigger. 395 func validateCustomTrigger(trigger *v1alpha1.CustomTrigger) error { 396 if trigger == nil { 397 return fmt.Errorf("custom trigger for can't be nil") 398 } 399 if trigger.ServerURL == "" { 400 return fmt.Errorf("custom trigger gRPC server url is not defined") 401 } 402 if trigger.Spec == nil { 403 return fmt.Errorf("trigger body can't be empty") 404 } 405 if trigger.Secure { 406 if trigger.CertSecret == nil { 407 return fmt.Errorf("certSecret can't be nil when the trigger server connection is secure") 408 } 409 } 410 if trigger.Parameters != nil { 411 for i, parameter := range trigger.Parameters { 412 if err := validateTriggerParameter(¶meter); err != nil { 413 return fmt.Errorf("resource parameter index: %d. err: %w", i, err) 414 } 415 } 416 } 417 return nil 418 } 419 420 // validateTriggerTemplateParameters validates resource and template parameters if any 421 func validateTriggerTemplateParameters(trigger *v1alpha1.Trigger) error { 422 if trigger.Parameters != nil { 423 for i, parameter := range trigger.Parameters { 424 if err := validateTriggerParameter(¶meter); err != nil { 425 return fmt.Errorf("template parameter index: %d. err: %w", i, err) 426 } 427 } 428 } 429 return nil 430 } 431 432 // validateTriggerParameter validates a trigger parameter 433 func validateTriggerParameter(parameter *v1alpha1.TriggerParameter) error { 434 if parameter.Src == nil { 435 return fmt.Errorf("parameter source can't be empty") 436 } 437 if parameter.Src.DependencyName == "" { 438 return fmt.Errorf("parameter dependency name can't be empty") 439 } 440 if parameter.Dest == "" { 441 return fmt.Errorf("parameter destination can't be empty") 442 } 443 444 switch op := parameter.Operation; op { 445 case v1alpha1.TriggerParameterOpAppend: 446 case v1alpha1.TriggerParameterOpOverwrite: 447 case v1alpha1.TriggerParameterOpPrepend: 448 case v1alpha1.TriggerParameterOpNone: 449 default: 450 return fmt.Errorf("parameter operation %+v is invalid", op) 451 } 452 453 return nil 454 } 455 456 // perform a check to see that each event dependency is in correct format and has valid filters set if any 457 func validateDependencies(eventDependencies []v1alpha1.EventDependency, b *eventbusv1alpha1.EventBus) error { 458 if len(eventDependencies) < 1 { 459 return fmt.Errorf("no event dependencies found") 460 } 461 462 comboKeys := make(map[string]bool) 463 for _, dep := range eventDependencies { 464 if dep.Name == "" { 465 return fmt.Errorf("event dependency must define a name") 466 } 467 if dep.EventSourceName == "" { 468 return fmt.Errorf("event dependency must define the EventSourceName") 469 } 470 471 if dep.EventName == "" { 472 return fmt.Errorf("event dependency must define the EventName") 473 } 474 if b.Spec.NATS != nil { 475 // For STAN, EventSourceName + EventName can not be referenced more than once in one Sensor object. 476 comboKey := fmt.Sprintf("%s-$$$-%s", dep.EventSourceName, dep.EventName) 477 if _, existing := comboKeys[comboKey]; existing { 478 return fmt.Errorf("event '%s' from EventSource '%s' is referenced for more than one dependency in this Sensor object", dep.EventName, dep.EventSourceName) 479 } 480 comboKeys[comboKey] = true 481 } 482 483 if err := validateEventFilter(dep.Filters); err != nil { 484 return err 485 } 486 487 if err := validateLogicalOperator(dep.FiltersLogicalOperator); err != nil { 488 return err 489 } 490 } 491 return nil 492 } 493 494 // validateLogicalOperator verifies that the logical operator in input is equal to a supported value 495 func validateLogicalOperator(logOp v1alpha1.LogicalOperator) error { 496 if logOp != v1alpha1.AndLogicalOperator && 497 logOp != v1alpha1.OrLogicalOperator && 498 logOp != v1alpha1.EmptyLogicalOperator { 499 return fmt.Errorf("logical operator %s not supported", logOp) 500 } 501 return nil 502 } 503 504 // validateComparator verifies that the comparator in input is equal to a supported value 505 func validateComparator(comp v1alpha1.Comparator) error { 506 if comp != v1alpha1.GreaterThanOrEqualTo && 507 comp != v1alpha1.GreaterThan && 508 comp != v1alpha1.EqualTo && 509 comp != v1alpha1.NotEqualTo && 510 comp != v1alpha1.LessThan && 511 comp != v1alpha1.LessThanOrEqualTo && 512 comp != v1alpha1.EmptyComparator { 513 return fmt.Errorf("comparator %s not supported", comp) 514 } 515 516 return nil 517 } 518 519 // validateEventFilter for a sensor 520 func validateEventFilter(filter *v1alpha1.EventDependencyFilter) error { 521 if filter == nil { 522 return nil 523 } 524 525 if err := validateLogicalOperator(filter.ExprLogicalOperator); err != nil { 526 return err 527 } 528 529 if err := validateLogicalOperator(filter.DataLogicalOperator); err != nil { 530 return err 531 } 532 533 if filter.Exprs != nil { 534 for _, expr := range filter.Exprs { 535 if err := validateEventExprFilter(&expr); err != nil { 536 return err 537 } 538 } 539 } 540 541 if filter.Data != nil { 542 for _, data := range filter.Data { 543 if err := validateEventDataFilter(&data); err != nil { 544 return err 545 } 546 } 547 } 548 549 if filter.Context != nil { 550 if err := validateEventCtxFilter(filter.Context); err != nil { 551 return err 552 } 553 } 554 555 if filter.Time != nil { 556 if err := validateEventTimeFilter(filter.Time); err != nil { 557 return err 558 } 559 } 560 return nil 561 } 562 563 // validateEventExprFilter validates context filter 564 func validateEventExprFilter(exprFilter *v1alpha1.ExprFilter) error { 565 if exprFilter.Expr == "" || 566 len(exprFilter.Fields) == 0 { 567 return fmt.Errorf("one of expr filters is not valid (expr and fields must be not empty)") 568 } 569 570 for _, fld := range exprFilter.Fields { 571 if fld.Path == "" || fld.Name == "" { 572 return fmt.Errorf("one of expr filters is not valid (path and name in a field must be not empty)") 573 } 574 } 575 576 return nil 577 } 578 579 // validateEventDataFilter validates context filter 580 func validateEventDataFilter(dataFilter *v1alpha1.DataFilter) error { 581 if dataFilter.Comparator != v1alpha1.EmptyComparator { 582 if err := validateComparator(dataFilter.Comparator); err != nil { 583 return err 584 } 585 } 586 587 if dataFilter.Path == "" || 588 dataFilter.Type == "" || 589 len(dataFilter.Value) == 0 { 590 return fmt.Errorf("one of data filters is not valid (type, path and value must be not empty)") 591 } 592 593 for _, val := range dataFilter.Value { 594 if val == "" { 595 return fmt.Errorf("one of data filters is not valid (value must be not empty)") 596 } 597 } 598 599 return nil 600 } 601 602 // validateEventCtxFilter validates context filter 603 func validateEventCtxFilter(ctxFilter *v1alpha1.EventContext) error { 604 if ctxFilter.Type == "" && 605 ctxFilter.Subject == "" && 606 ctxFilter.Source == "" && 607 ctxFilter.DataContentType == "" { 608 return fmt.Errorf("no fields specified in ctx filter (aka all events will be discarded)") 609 } 610 return nil 611 } 612 613 // validateEventTimeFilter validates time filter 614 func validateEventTimeFilter(timeFilter *v1alpha1.TimeFilter) error { 615 now := time.Now().UTC() 616 617 // Parse start and stop 618 startTime, startErr := common.ParseTime(timeFilter.Start, now) 619 if startErr != nil { 620 return startErr 621 } 622 stopTime, stopErr := common.ParseTime(timeFilter.Stop, now) 623 if stopErr != nil { 624 return stopErr 625 } 626 627 if stopTime.Equal(startTime) { 628 return fmt.Errorf("invalid event time filter: stop '%s' is equal to start '%s", timeFilter.Stop, timeFilter.Start) 629 } 630 return nil 631 } 632 633 // validateTriggerPolicy validates a trigger policy 634 func validateTriggerPolicy(trigger *v1alpha1.Trigger) error { 635 if trigger.Policy == nil { 636 return nil 637 } 638 if trigger.Template.K8s != nil { 639 return validateK8sTriggerPolicy(trigger.Policy.K8s) 640 } 641 if trigger.Template.ArgoWorkflow != nil { 642 return validateK8sTriggerPolicy(trigger.Policy.K8s) 643 } 644 if trigger.Template.HTTP != nil { 645 return validateStatusPolicy(trigger.Policy.Status) 646 } 647 if trigger.Template.AWSLambda != nil { 648 return validateStatusPolicy(trigger.Policy.Status) 649 } 650 return nil 651 } 652 653 // validateK8sTriggerPolicy validates a k8s trigger policy 654 func validateK8sTriggerPolicy(policy *v1alpha1.K8SResourcePolicy) error { 655 if policy == nil { 656 return nil 657 } 658 if policy.Labels == nil { 659 return fmt.Errorf("resource labels are not specified") 660 } 661 return nil 662 } 663 664 // validateStatusPolicy validates a http trigger policy 665 func validateStatusPolicy(policy *v1alpha1.StatusPolicy) error { 666 if policy == nil { 667 return nil 668 } 669 if policy.Allow == nil { 670 return fmt.Errorf("list of allowed response status is not specified") 671 } 672 return nil 673 }