@@ -2781,6 +2781,252 @@ resource "test_object" "a" {
2781
2781
}
2782
2782
},
2783
2783
},
2784
+ "action expansion with unknown instances" : {
2785
+ module : map [string ]string {
2786
+ "main.tf" : `
2787
+ variable "each" {
2788
+ type = set(string)
2789
+ }
2790
+ action "test_unlinked" "hello" {
2791
+ for_each = var.each
2792
+ }
2793
+ resource "test_object" "a" {
2794
+ lifecycle {
2795
+ action_trigger {
2796
+ events = [before_create]
2797
+ actions = [action.test_unlinked.hello["a"]]
2798
+ }
2799
+ }
2800
+ }
2801
+ ` ,
2802
+ },
2803
+ expectPlanActionCalled : false ,
2804
+ planOpts : & PlanOpts {
2805
+ Mode : plans .NormalMode ,
2806
+ DeferralAllowed : true ,
2807
+ SetVariables : InputValues {
2808
+ "each" : & InputValue {
2809
+ Value : cty .UnknownVal (cty .Set (cty .String )),
2810
+ SourceType : ValueFromCLIArg ,
2811
+ },
2812
+ },
2813
+ },
2814
+ assertPlan : func (t * testing.T , p * plans.Plan ) {
2815
+ if len (p .DeferredActionInvocations ) != 1 {
2816
+ t .Fatalf ("expected exactly one invocation, and found %d" , len (p .DeferredActionInvocations ))
2817
+ }
2818
+
2819
+ if p .DeferredActionInvocations [0 ].DeferredReason != providers .DeferredReasonDeferredPrereq {
2820
+ t .Fatalf ("expected.DeferredReasonDeferredPrereq, got %s" , p .DeferredActionInvocations [0 ].DeferredReason )
2821
+ }
2822
+
2823
+ ai := p .DeferredActionInvocations [0 ].ActionInvocationInstanceSrc
2824
+ if ai .Addr .String () != `action.test_unlinked.hello["a"]` {
2825
+ t .Fatalf (`expected action invocation for action.test_unlinked.hello["a"], got %s` , ai .Addr .String ())
2826
+ }
2827
+ },
2828
+ },
2829
+ "action with unknown module expansion" : {
2830
+ // We have an unknown module expansion (for_each over an unknown value). The
2831
+ // action and its triggering resource both live inside the (currently
2832
+ // un-expanded) module instances. Since we cannot expand the module yet, the
2833
+ // action invocation must be deferred.
2834
+ module : map [string ]string {
2835
+ "main.tf" : `
2836
+ variable "mods" {
2837
+ type = set(string)
2838
+ }
2839
+ module "mod" {
2840
+ source = "./mod"
2841
+ for_each = var.mods
2842
+ }
2843
+ ` ,
2844
+ "mod/mod.tf" : `
2845
+ action "test_unlinked" "hello" {
2846
+ config {
2847
+ attr = "static"
2848
+ }
2849
+ }
2850
+ resource "other_object" "a" {
2851
+ lifecycle {
2852
+ action_trigger {
2853
+ events = [before_create]
2854
+ actions = [action.test_unlinked.hello]
2855
+ }
2856
+ }
2857
+ }
2858
+ ` ,
2859
+ },
2860
+ expectPlanActionCalled : true ,
2861
+ planOpts : & PlanOpts {
2862
+ Mode : plans .NormalMode ,
2863
+ DeferralAllowed : true ,
2864
+ SetVariables : InputValues {
2865
+ "mods" : & InputValue {
2866
+ Value : cty .UnknownVal (cty .Set (cty .String )),
2867
+ SourceType : ValueFromCLIArg ,
2868
+ },
2869
+ },
2870
+ },
2871
+ assertPlan : func (t * testing.T , p * plans.Plan ) {
2872
+ // No concrete action invocations can be produced yet.
2873
+ if got := len (p .Changes .ActionInvocations ); got != 0 {
2874
+ t .Fatalf ("expected 0 planned action invocations, got %d" , got )
2875
+ }
2876
+
2877
+ if got := len (p .DeferredActionInvocations ); got != 0 {
2878
+ t .Fatalf ("expected 0 deferred action invocations, got %d" , got )
2879
+ }
2880
+
2881
+ if got := len (p .DeferredPartialActionInvocations ); got != 1 {
2882
+ t .Fatalf ("expected 1 deferred action invocations, got %d" , got )
2883
+ }
2884
+ ac , err := p .DeferredPartialActionInvocations [0 ].Decode (& unlinkedActionSchema )
2885
+ if err != nil {
2886
+ t .Fatalf ("error decoding action invocation: %s" , err )
2887
+ }
2888
+ if ac .DeferredReason != providers .DeferredReasonInstanceCountUnknown {
2889
+ t .Fatalf ("expected DeferredReasonInstanceCountUnknown, got %s" , ac .DeferredReason )
2890
+ }
2891
+ if ac .ActionInvocationInstance .ConfigValue .GetAttr ("attr" ).AsString () != "static" {
2892
+ t .Fatalf ("expected attr to be static, got %s" , ac .ActionInvocationInstance .ConfigValue .GetAttr ("attr" ).AsString ())
2893
+ }
2894
+
2895
+ },
2896
+ },
2897
+ "action with unknown module expansion and unknown instances" : {
2898
+ // Here both the module expansion and the action for_each expansion are unknown.
2899
+ // The action is referenced (with a specific key) inside the module so we should
2900
+ // get a single deferred action invocation for that specific (yet still
2901
+ // unresolved) instance address.
2902
+ module : map [string ]string {
2903
+ "main.tf" : `
2904
+ variable "mods" {
2905
+ type = set(string)
2906
+ }
2907
+ variable "actions" {
2908
+ type = set(string)
2909
+ }
2910
+ module "mod" {
2911
+ source = "./mod"
2912
+ for_each = var.mods
2913
+
2914
+ actions = var.actions
2915
+ }
2916
+ ` ,
2917
+ "mod/mod.tf" : `
2918
+ variable "actions" {
2919
+ type = set(string)
2920
+ }
2921
+ action "test_unlinked" "hello" {
2922
+ // Unknown for_each inside the module instance.
2923
+ for_each = var.actions
2924
+ }
2925
+ resource "other_object" "a" {
2926
+ lifecycle {
2927
+ action_trigger {
2928
+ events = [before_create]
2929
+ // We reference a specific (yet unknown) action instance key.
2930
+ actions = [action.test_unlinked.hello["a"]]
2931
+ }
2932
+ }
2933
+ }
2934
+ ` ,
2935
+ },
2936
+ expectPlanActionCalled : true ,
2937
+ planOpts : & PlanOpts {
2938
+ Mode : plans .NormalMode ,
2939
+ DeferralAllowed : true ,
2940
+ SetVariables : InputValues {
2941
+ "mods" : & InputValue {
2942
+ Value : cty .UnknownVal (cty .Set (cty .String )),
2943
+ SourceType : ValueFromCLIArg ,
2944
+ },
2945
+ "actions" : & InputValue {
2946
+ Value : cty .UnknownVal (cty .Set (cty .String )),
2947
+ SourceType : ValueFromCLIArg ,
2948
+ },
2949
+ },
2950
+ },
2951
+ assertPlan : func (t * testing.T , p * plans.Plan ) {
2952
+ if len (p .Changes .ActionInvocations ) != 0 {
2953
+ t .Fatalf ("expected 0 planned action invocations, got %d" , len (p .Changes .ActionInvocations ))
2954
+ }
2955
+ if got := len (p .DeferredActionInvocations ); got != 0 {
2956
+ t .Fatalf ("expected 0 deferred action invocations, got %d" , got )
2957
+ }
2958
+
2959
+ if len (p .DeferredPartialActionInvocations ) != 1 {
2960
+ t .Fatalf ("expected 1 deferred partial action invocations, got %d" , len (p .DeferredPartialActionInvocations ))
2961
+ }
2962
+
2963
+ ac , err := p .DeferredPartialActionInvocations [0 ].Decode (& unlinkedActionSchema )
2964
+ if err != nil {
2965
+ t .Fatalf ("error decoding action invocation: %s" , err )
2966
+ }
2967
+ if ac .DeferredReason != providers .DeferredReasonInstanceCountUnknown {
2968
+ t .Fatalf ("expected deferred reason to be DeferredReasonInstanceCountUnknown, got %s" , ac .DeferredReason )
2969
+ }
2970
+ if ! ac .ActionInvocationInstance .ConfigValue .IsNull () {
2971
+ t .Fatalf ("expected config value to be null" )
2972
+ }
2973
+ },
2974
+ },
2975
+
2976
+ "deferring resource dependencies should defer action" : {
2977
+ module : map [string ]string {
2978
+ "main.tf" : `
2979
+ resource "test_object" "origin" {
2980
+ name = "origin"
2981
+ }
2982
+ action "test_unlinked" "hello" {
2983
+ config {
2984
+ attr = test_object.origin.name
2985
+ }
2986
+ }
2987
+ resource "test_object" "a" {
2988
+ name = "a"
2989
+ lifecycle {
2990
+ action_trigger {
2991
+ events = [after_create]
2992
+ actions = [action.test_unlinked.hello]
2993
+ }
2994
+ }
2995
+ }
2996
+ ` ,
2997
+ },
2998
+ expectPlanActionCalled : false ,
2999
+ planOpts : & PlanOpts {
3000
+ Mode : plans .NormalMode ,
3001
+ DeferralAllowed : true ,
3002
+ },
3003
+ planResourceFn : func (t * testing.T , req providers.PlanResourceChangeRequest ) providers.PlanResourceChangeResponse {
3004
+ if req .Config .GetAttr ("name" ).AsString () == "origin" {
3005
+ return providers.PlanResourceChangeResponse {
3006
+ Deferred : & providers.Deferred {
3007
+ Reason : providers .DeferredReasonAbsentPrereq ,
3008
+ },
3009
+ }
3010
+ }
3011
+ return providers.PlanResourceChangeResponse {
3012
+ PlannedState : req .ProposedNewState ,
3013
+ PlannedPrivate : req .PriorPrivate ,
3014
+ PlannedIdentity : req .PriorIdentity ,
3015
+ }
3016
+ },
3017
+
3018
+ assertPlan : func (t * testing.T , p * plans.Plan ) {
3019
+ if len (p .DeferredActionInvocations ) != 1 {
3020
+ t .Errorf ("Expected 1 deferred action invocation, got %d" , len (p .DeferredActionInvocations ))
3021
+ }
3022
+ if p .DeferredActionInvocations [0 ].ActionInvocationInstanceSrc .Addr .String () != "action.test_unlinked.hello" {
3023
+ t .Errorf ("Expected action.test_unlinked.hello, got %s" , p .DeferredActionInvocations [0 ].ActionInvocationInstanceSrc .Addr .String ())
3024
+ }
3025
+ if p .DeferredActionInvocations [0 ].DeferredReason != providers .DeferredReasonDeferredPrereq {
3026
+ t .Errorf ("Expected DeferredReasonDeferredPrereq, got %s" , p .DeferredActionInvocations [0 ].DeferredReason )
3027
+ }
3028
+ },
3029
+ },
2784
3030
} {
2785
3031
t .Run (name , func (t * testing.T ) {
2786
3032
if tc .toBeImplemented {
0 commit comments