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  }