| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package schema |
| |
| import ( |
| "context" |
| "fmt" |
| "reflect" |
| "testing" |
| "time" |
| |
| "github.com/hashicorp/terraform/internal/legacy/terraform" |
| ) |
| |
| func TestProvisioner_impl(t *testing.T) { |
| var _ terraform.ResourceProvisioner = new(Provisioner) |
| } |
| |
| func noopApply(ctx context.Context) error { |
| return nil |
| } |
| |
| func TestProvisionerValidate(t *testing.T) { |
| cases := []struct { |
| Name string |
| P *Provisioner |
| Config map[string]interface{} |
| Err bool |
| Warns []string |
| }{ |
| { |
| Name: "No ApplyFunc", |
| P: &Provisioner{}, |
| Config: nil, |
| Err: true, |
| }, |
| { |
| Name: "Incorrect schema", |
| P: &Provisioner{ |
| Schema: map[string]*Schema{ |
| "foo": {}, |
| }, |
| ApplyFunc: noopApply, |
| }, |
| Config: nil, |
| Err: true, |
| }, |
| { |
| "Basic required field", |
| &Provisioner{ |
| Schema: map[string]*Schema{ |
| "foo": &Schema{ |
| Required: true, |
| Type: TypeString, |
| }, |
| }, |
| ApplyFunc: noopApply, |
| }, |
| nil, |
| true, |
| nil, |
| }, |
| |
| { |
| "Basic required field set", |
| &Provisioner{ |
| Schema: map[string]*Schema{ |
| "foo": &Schema{ |
| Required: true, |
| Type: TypeString, |
| }, |
| }, |
| ApplyFunc: noopApply, |
| }, |
| map[string]interface{}{ |
| "foo": "bar", |
| }, |
| false, |
| nil, |
| }, |
| { |
| Name: "Warning from property validation", |
| P: &Provisioner{ |
| Schema: map[string]*Schema{ |
| "foo": { |
| Type: TypeString, |
| Optional: true, |
| ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { |
| ws = append(ws, "Simple warning from property validation") |
| return |
| }, |
| }, |
| }, |
| ApplyFunc: noopApply, |
| }, |
| Config: map[string]interface{}{ |
| "foo": "", |
| }, |
| Err: false, |
| Warns: []string{"Simple warning from property validation"}, |
| }, |
| { |
| Name: "No schema", |
| P: &Provisioner{ |
| Schema: nil, |
| ApplyFunc: noopApply, |
| }, |
| Config: nil, |
| Err: false, |
| }, |
| { |
| Name: "Warning from provisioner ValidateFunc", |
| P: &Provisioner{ |
| Schema: nil, |
| ApplyFunc: noopApply, |
| ValidateFunc: func(*terraform.ResourceConfig) (ws []string, errors []error) { |
| ws = append(ws, "Simple warning from provisioner ValidateFunc") |
| return |
| }, |
| }, |
| Config: nil, |
| Err: false, |
| Warns: []string{"Simple warning from provisioner ValidateFunc"}, |
| }, |
| } |
| |
| for i, tc := range cases { |
| t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { |
| c := terraform.NewResourceConfigRaw(tc.Config) |
| ws, es := tc.P.Validate(c) |
| if len(es) > 0 != tc.Err { |
| t.Fatalf("%d: %#v %s", i, es, es) |
| } |
| if (tc.Warns != nil || len(ws) != 0) && !reflect.DeepEqual(ws, tc.Warns) { |
| t.Fatalf("%d: warnings mismatch, actual: %#v", i, ws) |
| } |
| }) |
| } |
| } |
| |
| func TestProvisionerApply(t *testing.T) { |
| cases := []struct { |
| Name string |
| P *Provisioner |
| Conn map[string]string |
| Config map[string]interface{} |
| Err bool |
| }{ |
| { |
| "Basic config", |
| &Provisioner{ |
| ConnSchema: map[string]*Schema{ |
| "foo": &Schema{ |
| Type: TypeString, |
| Optional: true, |
| }, |
| }, |
| |
| Schema: map[string]*Schema{ |
| "foo": &Schema{ |
| Type: TypeInt, |
| Optional: true, |
| }, |
| }, |
| |
| ApplyFunc: func(ctx context.Context) error { |
| cd := ctx.Value(ProvConnDataKey).(*ResourceData) |
| d := ctx.Value(ProvConfigDataKey).(*ResourceData) |
| if d.Get("foo").(int) != 42 { |
| return fmt.Errorf("bad config data") |
| } |
| if cd.Get("foo").(string) != "bar" { |
| return fmt.Errorf("bad conn data") |
| } |
| |
| return nil |
| }, |
| }, |
| map[string]string{ |
| "foo": "bar", |
| }, |
| map[string]interface{}{ |
| "foo": 42, |
| }, |
| false, |
| }, |
| } |
| |
| for i, tc := range cases { |
| t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { |
| c := terraform.NewResourceConfigRaw(tc.Config) |
| |
| state := &terraform.InstanceState{ |
| Ephemeral: terraform.EphemeralState{ |
| ConnInfo: tc.Conn, |
| }, |
| } |
| |
| err := tc.P.Apply(nil, state, c) |
| if err != nil != tc.Err { |
| t.Fatalf("%d: %s", i, err) |
| } |
| }) |
| } |
| } |
| |
| func TestProvisionerApply_nilState(t *testing.T) { |
| p := &Provisioner{ |
| ConnSchema: map[string]*Schema{ |
| "foo": &Schema{ |
| Type: TypeString, |
| Optional: true, |
| }, |
| }, |
| |
| Schema: map[string]*Schema{ |
| "foo": &Schema{ |
| Type: TypeInt, |
| Optional: true, |
| }, |
| }, |
| |
| ApplyFunc: func(ctx context.Context) error { |
| return nil |
| }, |
| } |
| |
| conf := map[string]interface{}{ |
| "foo": 42, |
| } |
| |
| c := terraform.NewResourceConfigRaw(conf) |
| err := p.Apply(nil, nil, c) |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| } |
| |
| func TestProvisionerStop(t *testing.T) { |
| var p Provisioner |
| |
| // Verify stopch blocks |
| ch := p.StopContext().Done() |
| select { |
| case <-ch: |
| t.Fatal("should not be stopped") |
| case <-time.After(10 * time.Millisecond): |
| } |
| |
| // Stop it |
| if err := p.Stop(); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| select { |
| case <-ch: |
| case <-time.After(10 * time.Millisecond): |
| t.Fatal("should be stopped") |
| } |
| } |
| |
| func TestProvisionerStop_apply(t *testing.T) { |
| p := &Provisioner{ |
| ConnSchema: map[string]*Schema{ |
| "foo": &Schema{ |
| Type: TypeString, |
| Optional: true, |
| }, |
| }, |
| |
| Schema: map[string]*Schema{ |
| "foo": &Schema{ |
| Type: TypeInt, |
| Optional: true, |
| }, |
| }, |
| |
| ApplyFunc: func(ctx context.Context) error { |
| <-ctx.Done() |
| return nil |
| }, |
| } |
| |
| conn := map[string]string{ |
| "foo": "bar", |
| } |
| |
| conf := map[string]interface{}{ |
| "foo": 42, |
| } |
| |
| c := terraform.NewResourceConfigRaw(conf) |
| state := &terraform.InstanceState{ |
| Ephemeral: terraform.EphemeralState{ |
| ConnInfo: conn, |
| }, |
| } |
| |
| // Run the apply in a goroutine |
| doneCh := make(chan struct{}) |
| go func() { |
| p.Apply(nil, state, c) |
| close(doneCh) |
| }() |
| |
| // Should block |
| select { |
| case <-doneCh: |
| t.Fatal("should not be done") |
| case <-time.After(10 * time.Millisecond): |
| } |
| |
| // Stop! |
| p.Stop() |
| |
| select { |
| case <-doneCh: |
| case <-time.After(10 * time.Millisecond): |
| t.Fatal("should be done") |
| } |
| } |
| |
| func TestProvisionerStop_stopFirst(t *testing.T) { |
| var p Provisioner |
| |
| // Stop it |
| if err := p.Stop(); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| select { |
| case <-p.StopContext().Done(): |
| case <-time.After(10 * time.Millisecond): |
| t.Fatal("should be stopped") |
| } |
| } |