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