github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/provisioner_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 schema 7 8 import ( 9 "context" 10 "fmt" 11 "reflect" 12 "testing" 13 "time" 14 15 "github.com/opentofu/opentofu/internal/legacy/tofu" 16 ) 17 18 func TestProvisioner_impl(t *testing.T) { 19 var _ tofu.ResourceProvisioner = new(Provisioner) 20 } 21 22 func noopApply(ctx context.Context) error { 23 return nil 24 } 25 26 func TestProvisionerValidate(t *testing.T) { 27 cases := []struct { 28 Name string 29 P *Provisioner 30 Config map[string]interface{} 31 Err bool 32 Warns []string 33 }{ 34 { 35 Name: "No ApplyFunc", 36 P: &Provisioner{}, 37 Config: nil, 38 Err: true, 39 }, 40 { 41 Name: "Incorrect schema", 42 P: &Provisioner{ 43 Schema: map[string]*Schema{ 44 "foo": {}, 45 }, 46 ApplyFunc: noopApply, 47 }, 48 Config: nil, 49 Err: true, 50 }, 51 { 52 "Basic required field", 53 &Provisioner{ 54 Schema: map[string]*Schema{ 55 "foo": &Schema{ 56 Required: true, 57 Type: TypeString, 58 }, 59 }, 60 ApplyFunc: noopApply, 61 }, 62 nil, 63 true, 64 nil, 65 }, 66 67 { 68 "Basic required field set", 69 &Provisioner{ 70 Schema: map[string]*Schema{ 71 "foo": &Schema{ 72 Required: true, 73 Type: TypeString, 74 }, 75 }, 76 ApplyFunc: noopApply, 77 }, 78 map[string]interface{}{ 79 "foo": "bar", 80 }, 81 false, 82 nil, 83 }, 84 { 85 Name: "Warning from property validation", 86 P: &Provisioner{ 87 Schema: map[string]*Schema{ 88 "foo": { 89 Type: TypeString, 90 Optional: true, 91 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 92 ws = append(ws, "Simple warning from property validation") 93 return 94 }, 95 }, 96 }, 97 ApplyFunc: noopApply, 98 }, 99 Config: map[string]interface{}{ 100 "foo": "", 101 }, 102 Err: false, 103 Warns: []string{"Simple warning from property validation"}, 104 }, 105 { 106 Name: "No schema", 107 P: &Provisioner{ 108 Schema: nil, 109 ApplyFunc: noopApply, 110 }, 111 Config: nil, 112 Err: false, 113 }, 114 { 115 Name: "Warning from provisioner ValidateFunc", 116 P: &Provisioner{ 117 Schema: nil, 118 ApplyFunc: noopApply, 119 ValidateFunc: func(*tofu.ResourceConfig) (ws []string, errors []error) { 120 ws = append(ws, "Simple warning from provisioner ValidateFunc") 121 return 122 }, 123 }, 124 Config: nil, 125 Err: false, 126 Warns: []string{"Simple warning from provisioner ValidateFunc"}, 127 }, 128 } 129 130 for i, tc := range cases { 131 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 132 c := tofu.NewResourceConfigRaw(tc.Config) 133 ws, es := tc.P.Validate(c) 134 if len(es) > 0 != tc.Err { 135 t.Fatalf("%d: %#v %s", i, es, es) 136 } 137 if (tc.Warns != nil || len(ws) != 0) && !reflect.DeepEqual(ws, tc.Warns) { 138 t.Fatalf("%d: warnings mismatch, actual: %#v", i, ws) 139 } 140 }) 141 } 142 } 143 144 func TestProvisionerApply(t *testing.T) { 145 cases := []struct { 146 Name string 147 P *Provisioner 148 Conn map[string]string 149 Config map[string]interface{} 150 Err bool 151 }{ 152 { 153 "Basic config", 154 &Provisioner{ 155 ConnSchema: map[string]*Schema{ 156 "foo": &Schema{ 157 Type: TypeString, 158 Optional: true, 159 }, 160 }, 161 162 Schema: map[string]*Schema{ 163 "foo": &Schema{ 164 Type: TypeInt, 165 Optional: true, 166 }, 167 }, 168 169 ApplyFunc: func(ctx context.Context) error { 170 cd := ctx.Value(ProvConnDataKey).(*ResourceData) 171 d := ctx.Value(ProvConfigDataKey).(*ResourceData) 172 if d.Get("foo").(int) != 42 { 173 return fmt.Errorf("bad config data") 174 } 175 if cd.Get("foo").(string) != "bar" { 176 return fmt.Errorf("bad conn data") 177 } 178 179 return nil 180 }, 181 }, 182 map[string]string{ 183 "foo": "bar", 184 }, 185 map[string]interface{}{ 186 "foo": 42, 187 }, 188 false, 189 }, 190 } 191 192 for i, tc := range cases { 193 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 194 c := tofu.NewResourceConfigRaw(tc.Config) 195 196 state := &tofu.InstanceState{ 197 Ephemeral: tofu.EphemeralState{ 198 ConnInfo: tc.Conn, 199 }, 200 } 201 202 err := tc.P.Apply(nil, state, c) 203 if err != nil != tc.Err { 204 t.Fatalf("%d: %s", i, err) 205 } 206 }) 207 } 208 } 209 210 func TestProvisionerApply_nilState(t *testing.T) { 211 p := &Provisioner{ 212 ConnSchema: map[string]*Schema{ 213 "foo": &Schema{ 214 Type: TypeString, 215 Optional: true, 216 }, 217 }, 218 219 Schema: map[string]*Schema{ 220 "foo": &Schema{ 221 Type: TypeInt, 222 Optional: true, 223 }, 224 }, 225 226 ApplyFunc: func(ctx context.Context) error { 227 return nil 228 }, 229 } 230 231 conf := map[string]interface{}{ 232 "foo": 42, 233 } 234 235 c := tofu.NewResourceConfigRaw(conf) 236 err := p.Apply(nil, nil, c) 237 if err != nil { 238 t.Fatalf("err: %s", err) 239 } 240 } 241 242 func TestProvisionerStop(t *testing.T) { 243 var p Provisioner 244 245 // Verify stopch blocks 246 ch := p.StopContext().Done() 247 select { 248 case <-ch: 249 t.Fatal("should not be stopped") 250 case <-time.After(10 * time.Millisecond): 251 } 252 253 // Stop it 254 if err := p.Stop(); err != nil { 255 t.Fatalf("err: %s", err) 256 } 257 258 select { 259 case <-ch: 260 case <-time.After(10 * time.Millisecond): 261 t.Fatal("should be stopped") 262 } 263 } 264 265 func TestProvisionerStop_apply(t *testing.T) { 266 p := &Provisioner{ 267 ConnSchema: map[string]*Schema{ 268 "foo": &Schema{ 269 Type: TypeString, 270 Optional: true, 271 }, 272 }, 273 274 Schema: map[string]*Schema{ 275 "foo": &Schema{ 276 Type: TypeInt, 277 Optional: true, 278 }, 279 }, 280 281 ApplyFunc: func(ctx context.Context) error { 282 <-ctx.Done() 283 return nil 284 }, 285 } 286 287 conn := map[string]string{ 288 "foo": "bar", 289 } 290 291 conf := map[string]interface{}{ 292 "foo": 42, 293 } 294 295 c := tofu.NewResourceConfigRaw(conf) 296 state := &tofu.InstanceState{ 297 Ephemeral: tofu.EphemeralState{ 298 ConnInfo: conn, 299 }, 300 } 301 302 // Run the apply in a goroutine 303 doneCh := make(chan struct{}) 304 go func() { 305 p.Apply(nil, state, c) 306 close(doneCh) 307 }() 308 309 // Should block 310 select { 311 case <-doneCh: 312 t.Fatal("should not be done") 313 case <-time.After(10 * time.Millisecond): 314 } 315 316 // Stop! 317 p.Stop() 318 319 select { 320 case <-doneCh: 321 case <-time.After(10 * time.Millisecond): 322 t.Fatal("should be done") 323 } 324 } 325 326 func TestProvisionerStop_stopFirst(t *testing.T) { 327 var p Provisioner 328 329 // Stop it 330 if err := p.Stop(); err != nil { 331 t.Fatalf("err: %s", err) 332 } 333 334 select { 335 case <-p.StopContext().Done(): 336 case <-time.After(10 * time.Millisecond): 337 t.Fatal("should be stopped") 338 } 339 }