github.com/opentofu/opentofu@v1.7.1/internal/checks/state_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package checks 7 8 import ( 9 "context" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 14 "github.com/opentofu/opentofu/internal/addrs" 15 "github.com/opentofu/opentofu/internal/configs/configload" 16 "github.com/opentofu/opentofu/internal/initwd" 17 ) 18 19 func TestChecksHappyPath(t *testing.T) { 20 const fixtureDir = "testdata/happypath" 21 loader, close := configload.NewLoaderForTests(t) 22 defer close() 23 inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, nil) 24 _, instDiags := inst.InstallModules(context.Background(), fixtureDir, "tests", true, false, initwd.ModuleInstallHooksImpl{}) 25 if instDiags.HasErrors() { 26 t.Fatal(instDiags.Err()) 27 } 28 if err := loader.RefreshModules(); err != nil { 29 t.Fatalf("failed to refresh modules after installation: %s", err) 30 } 31 32 ///////////////////////////////////////////////////////////////////////// 33 34 cfg, hclDiags := loader.LoadConfig(fixtureDir) 35 if hclDiags.HasErrors() { 36 t.Fatalf("invalid configuration: %s", hclDiags.Error()) 37 } 38 39 resourceA := addrs.Resource{ 40 Mode: addrs.ManagedResourceMode, 41 Type: "null_resource", 42 Name: "a", 43 }.InModule(addrs.RootModule) 44 resourceNoChecks := addrs.Resource{ 45 Mode: addrs.ManagedResourceMode, 46 Type: "null_resource", 47 Name: "no_checks", 48 }.InModule(addrs.RootModule) 49 resourceNonExist := addrs.Resource{ 50 Mode: addrs.ManagedResourceMode, 51 Type: "null_resource", 52 Name: "nonexist", 53 }.InModule(addrs.RootModule) 54 rootOutput := addrs.OutputValue{ 55 Name: "a", 56 }.InModule(addrs.RootModule) 57 moduleChild := addrs.RootModule.Child("child") 58 resourceB := addrs.Resource{ 59 Mode: addrs.ManagedResourceMode, 60 Type: "null_resource", 61 Name: "b", 62 }.InModule(moduleChild) 63 resourceC := addrs.Resource{ 64 Mode: addrs.ManagedResourceMode, 65 Type: "null_resource", 66 Name: "c", 67 }.InModule(moduleChild) 68 childOutput := addrs.OutputValue{ 69 Name: "b", 70 }.InModule(moduleChild) 71 checkBlock := addrs.Check{ 72 Name: "check", 73 }.InModule(addrs.RootModule) 74 75 // First some consistency checks to make sure our configuration is the 76 // shape we are relying on it to be. 77 if addr := resourceA; cfg.Module.ResourceByAddr(addr.Resource) == nil { 78 t.Fatalf("configuration does not include %s", addr) 79 } 80 if addr := resourceB; cfg.Children["child"].Module.ResourceByAddr(addr.Resource) == nil { 81 t.Fatalf("configuration does not include %s", addr) 82 } 83 if addr := resourceNoChecks; cfg.Module.ResourceByAddr(addr.Resource) == nil { 84 t.Fatalf("configuration does not include %s", addr) 85 } 86 if addr := resourceNonExist; cfg.Module.ResourceByAddr(addr.Resource) != nil { 87 t.Fatalf("configuration includes %s, which is not supposed to exist", addr) 88 } 89 if addr := checkBlock; cfg.Module.Checks[addr.Check.Name] == nil { 90 t.Fatalf("configuration does not include %s", addr) 91 } 92 93 ///////////////////////////////////////////////////////////////////////// 94 95 checks := NewState(cfg) 96 97 missing := 0 98 if addr := resourceA; !checks.ConfigHasChecks(addr) { 99 t.Errorf("checks not detected for %s", addr) 100 missing++ 101 } 102 if addr := resourceB; !checks.ConfigHasChecks(addr) { 103 t.Errorf("checks not detected for %s", addr) 104 missing++ 105 } 106 if addr := resourceC; !checks.ConfigHasChecks(addr) { 107 t.Errorf("checks not detected for %s", addr) 108 missing++ 109 } 110 if addr := rootOutput; !checks.ConfigHasChecks(addr) { 111 t.Errorf("checks not detected for %s", addr) 112 missing++ 113 } 114 if addr := childOutput; !checks.ConfigHasChecks(addr) { 115 t.Errorf("checks not detected for %s", addr) 116 missing++ 117 } 118 if addr := resourceNoChecks; checks.ConfigHasChecks(addr) { 119 t.Errorf("checks detected for %s, even though it has none", addr) 120 } 121 if addr := resourceNonExist; checks.ConfigHasChecks(addr) { 122 t.Errorf("checks detected for %s, even though it doesn't exist", addr) 123 } 124 if addr := checkBlock; !checks.ConfigHasChecks(addr) { 125 t.Errorf("checks not detected for %s", addr) 126 missing++ 127 } 128 if missing > 0 { 129 t.Fatalf("missing some configuration objects we'd need for subsequent testing") 130 } 131 132 ///////////////////////////////////////////////////////////////////////// 133 134 // Everything should start with status unknown. 135 136 { 137 wantConfigAddrs := addrs.MakeSet[addrs.ConfigCheckable]( 138 resourceA, 139 resourceB, 140 resourceC, 141 rootOutput, 142 childOutput, 143 checkBlock, 144 ) 145 gotConfigAddrs := checks.AllConfigAddrs() 146 if diff := cmp.Diff(wantConfigAddrs, gotConfigAddrs); diff != "" { 147 t.Errorf("wrong detected config addresses\n%s", diff) 148 } 149 150 for _, configAddr := range gotConfigAddrs { 151 if got, want := checks.AggregateCheckStatus(configAddr), StatusUnknown; got != want { 152 t.Errorf("incorrect initial aggregate check status for %s: %s, but want %s", configAddr, got, want) 153 } 154 } 155 } 156 157 ///////////////////////////////////////////////////////////////////////// 158 159 // The following are steps that would normally be done by OpenTofu Core 160 // as part of visiting checkable objects during the graph walk. We're 161 // simulating a likely sequence of calls here for testing purposes, but 162 // OpenTofu Core won't necessarily visit all of these in exactly the 163 // same order every time and so this is just one possible valid ordering 164 // of calls. 165 166 resourceInstA := resourceA.Resource.Absolute(addrs.RootModuleInstance).Instance(addrs.NoKey) 167 rootOutputInst := rootOutput.OutputValue.Absolute(addrs.RootModuleInstance) 168 moduleChildInst := addrs.RootModuleInstance.Child("child", addrs.NoKey) 169 resourceInstB := resourceB.Resource.Absolute(moduleChildInst).Instance(addrs.NoKey) 170 resourceInstC0 := resourceC.Resource.Absolute(moduleChildInst).Instance(addrs.IntKey(0)) 171 resourceInstC1 := resourceC.Resource.Absolute(moduleChildInst).Instance(addrs.IntKey(1)) 172 childOutputInst := childOutput.OutputValue.Absolute(moduleChildInst) 173 checkBlockInst := checkBlock.Check.Absolute(addrs.RootModuleInstance) 174 175 checks.ReportCheckableObjects(resourceA, addrs.MakeSet[addrs.Checkable](resourceInstA)) 176 checks.ReportCheckResult(resourceInstA, addrs.ResourcePrecondition, 0, StatusPass) 177 checks.ReportCheckResult(resourceInstA, addrs.ResourcePrecondition, 1, StatusPass) 178 checks.ReportCheckResult(resourceInstA, addrs.ResourcePostcondition, 0, StatusPass) 179 180 checks.ReportCheckableObjects(resourceB, addrs.MakeSet[addrs.Checkable](resourceInstB)) 181 checks.ReportCheckResult(resourceInstB, addrs.ResourcePrecondition, 0, StatusPass) 182 183 checks.ReportCheckableObjects(resourceC, addrs.MakeSet[addrs.Checkable](resourceInstC0, resourceInstC1)) 184 checks.ReportCheckResult(resourceInstC0, addrs.ResourcePostcondition, 0, StatusPass) 185 checks.ReportCheckResult(resourceInstC1, addrs.ResourcePostcondition, 0, StatusPass) 186 187 checks.ReportCheckableObjects(childOutput, addrs.MakeSet[addrs.Checkable](childOutputInst)) 188 checks.ReportCheckResult(childOutputInst, addrs.OutputPrecondition, 0, StatusPass) 189 190 checks.ReportCheckableObjects(rootOutput, addrs.MakeSet[addrs.Checkable](rootOutputInst)) 191 checks.ReportCheckResult(rootOutputInst, addrs.OutputPrecondition, 0, StatusPass) 192 193 checks.ReportCheckableObjects(checkBlock, addrs.MakeSet[addrs.Checkable](checkBlockInst)) 194 checks.ReportCheckResult(checkBlockInst, addrs.CheckAssertion, 0, StatusPass) 195 196 ///////////////////////////////////////////////////////////////////////// 197 198 // This "section" is simulating what we might do to report the results 199 // of the checks after a run completes. 200 201 { 202 configCount := 0 203 for _, configAddr := range checks.AllConfigAddrs() { 204 configCount++ 205 if got, want := checks.AggregateCheckStatus(configAddr), StatusPass; got != want { 206 t.Errorf("incorrect final aggregate check status for %s: %s, but want %s", configAddr, got, want) 207 } 208 } 209 if got, want := configCount, 6; got != want { 210 t.Errorf("incorrect number of known config addresses %d; want %d", got, want) 211 } 212 } 213 214 { 215 objAddrs := addrs.MakeSet[addrs.Checkable]( 216 resourceInstA, 217 rootOutputInst, 218 resourceInstB, 219 resourceInstC0, 220 resourceInstC1, 221 childOutputInst, 222 checkBlockInst, 223 ) 224 for _, addr := range objAddrs { 225 if got, want := checks.ObjectCheckStatus(addr), StatusPass; got != want { 226 t.Errorf("incorrect final check status for object %s: %s, but want %s", addr, got, want) 227 } 228 } 229 } 230 }