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  }