@@ -384,17 +384,15 @@ func TestRunFunction(t *testing.T) {
384
384
},
385
385
},
386
386
},
387
- "FailedPatchNotSaved " : {
388
- reason : "If we fail to patch a desired resource produced by a previous Function in the pipeline we should return a fatal result ." ,
387
+ "OptionalFieldPathNotFound " : {
388
+ reason : "If we fail to patch a desired resource because an optional field path was not found we should skip the patch ." ,
389
389
args : args {
390
390
req : & fnv1beta1.RunFunctionRequest {
391
391
Input : resource .MustStructObject (& v1beta1.Resources {
392
392
Resources : []v1beta1.ComposedTemplate {
393
393
{
394
- // This template base no base, so we try to
395
- // patch the resource named "cool-resource" in
396
- // the desired resources array.
397
394
Name : "cool-resource" ,
395
+ Base : & runtime.RawExtension {Raw : []byte (`{"apiVersion":"example.org/v1","kind":"CD","spec":{}}` )},
398
396
Patches : []v1beta1.ComposedPatch {
399
397
{
400
398
// This patch should work.
@@ -405,11 +403,11 @@ func TestRunFunction(t *testing.T) {
405
403
},
406
404
},
407
405
{
408
- // This patch should return an error,
409
- // because the path is not an array.
406
+ // This patch should be skipped, because
407
+ // the path is not found
410
408
Type : v1beta1 .PatchTypeFromCompositeFieldPath ,
411
409
Patch : v1beta1.Patch {
412
- FromFieldPath : ptr.To [string ]("spec.widgets[0] " ),
410
+ FromFieldPath : ptr.To [string ]("spec.doesNotExist " ),
413
411
},
414
412
},
415
413
},
@@ -421,16 +419,94 @@ func TestRunFunction(t *testing.T) {
421
419
Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}` ),
422
420
},
423
421
},
422
+ },
423
+ },
424
+ want : want {
425
+ rsp : & fnv1beta1.RunFunctionResponse {
426
+ Meta : & fnv1beta1.ResponseMeta {Ttl : durationpb .New (response .DefaultTTL )},
424
427
Desired : & fnv1beta1.State {
425
428
Composite : & fnv1beta1.Resource {
426
- Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"} }` ),
429
+ Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"XR"}` ),
427
430
},
428
431
Resources : map [string ]* fnv1beta1.Resource {
429
432
"cool-resource" : {
430
- Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"CD","spec":{"watchers":42}}` ),
433
+ // Watchers becomes "10" because our first patch
434
+ // worked. We only skipped the second patch.
435
+ Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"CD","spec":{"watchers":"10"}}` ),
431
436
},
432
437
},
433
438
},
439
+ Context : & structpb.Struct {Fields : map [string ]* structpb.Value {fncontext .KeyEnvironment : structpb .NewStructValue (nil )}},
440
+ },
441
+ },
442
+ },
443
+ "RequiredFieldPathNotFound" : {
444
+ reason : "If we fail to patch a desired resource because a required field path was not found, and the resource doesn't exist, we should not add it to desired state (i.e. create it)." ,
445
+ args : args {
446
+ req : & fnv1beta1.RunFunctionRequest {
447
+ Input : resource .MustStructObject (& v1beta1.Resources {
448
+ Resources : []v1beta1.ComposedTemplate {
449
+ {
450
+ Name : "new-resource" ,
451
+ Base : & runtime.RawExtension {Raw : []byte (`{"apiVersion":"example.org/v1","kind":"CD","spec":{}}` )},
452
+ Patches : []v1beta1.ComposedPatch {
453
+ {
454
+ // This patch will fail because the path
455
+ // is not found.
456
+ Type : v1beta1 .PatchTypeFromCompositeFieldPath ,
457
+ Patch : v1beta1.Patch {
458
+ FromFieldPath : ptr.To [string ]("spec.doesNotExist" ),
459
+ Policy : & v1beta1.PatchPolicy {
460
+ FromFieldPath : ptr.To [v1beta1.FromFieldPathPolicy ](v1beta1 .FromFieldPathPolicyRequired ),
461
+ },
462
+ },
463
+ },
464
+ },
465
+ },
466
+ {
467
+ Name : "existing-resource" ,
468
+ Base : & runtime.RawExtension {Raw : []byte (`{"apiVersion":"example.org/v1","kind":"CD","spec":{}}` )},
469
+ Patches : []v1beta1.ComposedPatch {
470
+ {
471
+ // This patch should work.
472
+ Type : v1beta1 .PatchTypeFromCompositeFieldPath ,
473
+ Patch : v1beta1.Patch {
474
+ FromFieldPath : ptr.To [string ]("spec.widgets" ),
475
+ ToFieldPath : ptr.To [string ]("spec.watchers" ),
476
+ },
477
+ },
478
+ {
479
+ // This patch will fail because the path
480
+ // is not found.
481
+ Type : v1beta1 .PatchTypeFromCompositeFieldPath ,
482
+ Patch : v1beta1.Patch {
483
+ FromFieldPath : ptr.To [string ]("spec.doesNotExist" ),
484
+ Policy : & v1beta1.PatchPolicy {
485
+ FromFieldPath : ptr.To [v1beta1.FromFieldPathPolicy ](v1beta1 .FromFieldPathPolicyRequired ),
486
+ },
487
+ },
488
+ },
489
+ },
490
+ },
491
+ },
492
+ }),
493
+ Observed : & fnv1beta1.State {
494
+ Composite : & fnv1beta1.Resource {
495
+ Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}` ),
496
+ },
497
+ Resources : map [string ]* fnv1beta1.Resource {
498
+ // "existing-resource" exists.
499
+ "existing-resource" : {},
500
+
501
+ // Note "new-resource" doesn't appear in the
502
+ // observed resources. It doesn't yet exist.
503
+ },
504
+ },
505
+ Desired : & fnv1beta1.State {
506
+ Composite : & fnv1beta1.Resource {
507
+ Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}` ),
508
+ },
509
+ },
434
510
},
435
511
},
436
512
want : want {
@@ -441,11 +517,77 @@ func TestRunFunction(t *testing.T) {
441
517
Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}` ),
442
518
},
443
519
Resources : map [string ]* fnv1beta1.Resource {
444
- "cool-resource" : {
445
- // spec.watchers would be "10" if we didn't
446
- // discard the patch that worked.
447
- Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"CD","spec":{"watchers":42 }}` ),
520
+ // Note that the first patch did work. We only
521
+ // skipped the patch from the required field path.
522
+ "existing-resource" : {
523
+ Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"CD","spec":{"watchers":"10" }}` ),
448
524
},
525
+
526
+ // Note "new-resource" doesn't appear here.
527
+ },
528
+ },
529
+ Context : & structpb.Struct {Fields : map [string ]* structpb.Value {fncontext .KeyEnvironment : structpb .NewStructValue (nil )}},
530
+ Results : []* fnv1beta1.Result {
531
+ {
532
+ Severity : fnv1beta1 .Severity_SEVERITY_WARNING ,
533
+ Message : `not adding new composed resource "new-resource" to desired state because "FromCompositeFieldPath" patch at index 0 has 'policy.fromFieldPath: Required': spec.doesNotExist: no such field` ,
534
+ },
535
+ {
536
+ Severity : fnv1beta1 .Severity_SEVERITY_WARNING ,
537
+ Message : `cannot render composed resource "existing-resource" "FromCompositeFieldPath" patch at index 1: ignoring 'policy.fromFieldPath: Required' because 'to' resource already exists: spec.doesNotExist: no such field` ,
538
+ },
539
+ },
540
+ },
541
+ },
542
+ },
543
+ "PatchErrorIsFatal" : {
544
+ reason : "If we fail to patch a desired resource we should return a fatal result." ,
545
+ args : args {
546
+ req : & fnv1beta1.RunFunctionRequest {
547
+ Input : resource .MustStructObject (& v1beta1.Resources {
548
+ Resources : []v1beta1.ComposedTemplate {
549
+ {
550
+ Name : "cool-resource" ,
551
+ Base : & runtime.RawExtension {Raw : []byte (`{"apiVersion":"example.org/v1","kind":"CD","spec":{}}` )},
552
+ Patches : []v1beta1.ComposedPatch {
553
+ {
554
+ // This patch should work.
555
+ Type : v1beta1 .PatchTypeFromCompositeFieldPath ,
556
+ Patch : v1beta1.Patch {
557
+ FromFieldPath : ptr.To [string ]("spec.widgets" ),
558
+ ToFieldPath : ptr.To [string ]("spec.watchers" ),
559
+ },
560
+ },
561
+ {
562
+ // This patch should return an error,
563
+ // because the path is not an array.
564
+ Type : v1beta1 .PatchTypeFromCompositeFieldPath ,
565
+ Patch : v1beta1.Patch {
566
+ FromFieldPath : ptr.To [string ]("spec.widgets[0]" ),
567
+ },
568
+ },
569
+ },
570
+ },
571
+ },
572
+ }),
573
+ Observed : & fnv1beta1.State {
574
+ Composite : & fnv1beta1.Resource {
575
+ Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}` ),
576
+ },
577
+ },
578
+ Desired : & fnv1beta1.State {
579
+ Composite : & fnv1beta1.Resource {
580
+ Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}` ),
581
+ },
582
+ },
583
+ },
584
+ },
585
+ want : want {
586
+ rsp : & fnv1beta1.RunFunctionResponse {
587
+ Meta : & fnv1beta1.ResponseMeta {Ttl : durationpb .New (response .DefaultTTL )},
588
+ Desired : & fnv1beta1.State {
589
+ Composite : & fnv1beta1.Resource {
590
+ Resource : resource .MustStructJSON (`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}` ),
449
591
},
450
592
},
451
593
Results : []* fnv1beta1.Result {
0 commit comments