github.com/pulumi/terraform@v1.4.0/pkg/builtin/provisioners/file/resource_provisioner.go (about)

     1  package file
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  
    10  	"github.com/pulumi/terraform/pkg/communicator"
    11  	"github.com/pulumi/terraform/pkg/configs/configschema"
    12  	"github.com/pulumi/terraform/pkg/provisioners"
    13  	"github.com/pulumi/terraform/pkg/tfdiags"
    14  	"github.com/mitchellh/go-homedir"
    15  	"github.com/zclconf/go-cty/cty"
    16  )
    17  
    18  func New() provisioners.Interface {
    19  	ctx, cancel := context.WithCancel(context.Background())
    20  	return &provisioner{
    21  		ctx:    ctx,
    22  		cancel: cancel,
    23  	}
    24  }
    25  
    26  type provisioner struct {
    27  	// We store a context here tied to the lifetime of the provisioner.
    28  	// This allows the Stop method to cancel any in-flight requests.
    29  	ctx    context.Context
    30  	cancel context.CancelFunc
    31  }
    32  
    33  func (p *provisioner) GetSchema() (resp provisioners.GetSchemaResponse) {
    34  	schema := &configschema.Block{
    35  		Attributes: map[string]*configschema.Attribute{
    36  			"source": {
    37  				Type:     cty.String,
    38  				Optional: true,
    39  			},
    40  
    41  			"content": {
    42  				Type:     cty.String,
    43  				Optional: true,
    44  			},
    45  
    46  			"destination": {
    47  				Type:     cty.String,
    48  				Required: true,
    49  			},
    50  		},
    51  	}
    52  	resp.Provisioner = schema
    53  	return resp
    54  }
    55  
    56  func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) {
    57  	cfg, err := p.GetSchema().Provisioner.CoerceValue(req.Config)
    58  	if err != nil {
    59  		resp.Diagnostics = resp.Diagnostics.Append(err)
    60  	}
    61  
    62  	source := cfg.GetAttr("source")
    63  	content := cfg.GetAttr("content")
    64  
    65  	switch {
    66  	case !source.IsNull() && !content.IsNull():
    67  		resp.Diagnostics = resp.Diagnostics.Append(errors.New("Cannot set both 'source' and 'content'"))
    68  		return resp
    69  	case source.IsNull() && content.IsNull():
    70  		resp.Diagnostics = resp.Diagnostics.Append(errors.New("Must provide one of 'source' or 'content'"))
    71  		return resp
    72  	}
    73  
    74  	return resp
    75  }
    76  
    77  func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
    78  	if req.Connection.IsNull() {
    79  		resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
    80  			tfdiags.Error,
    81  			"file provisioner error",
    82  			"Missing connection configuration for provisioner.",
    83  		))
    84  		return resp
    85  	}
    86  
    87  	comm, err := communicator.New(req.Connection)
    88  	if err != nil {
    89  		resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
    90  			tfdiags.Error,
    91  			"file provisioner error",
    92  			err.Error(),
    93  		))
    94  		return resp
    95  	}
    96  
    97  	// Get the source
    98  	src, deleteSource, err := getSrc(req.Config)
    99  	if err != nil {
   100  		resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
   101  			tfdiags.Error,
   102  			"file provisioner error",
   103  			err.Error(),
   104  		))
   105  		return resp
   106  	}
   107  	if deleteSource {
   108  		defer os.Remove(src)
   109  	}
   110  
   111  	// Begin the file copy
   112  	dst := req.Config.GetAttr("destination").AsString()
   113  	if err := copyFiles(p.ctx, comm, src, dst); err != nil {
   114  		resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
   115  			tfdiags.Error,
   116  			"file provisioner error",
   117  			err.Error(),
   118  		))
   119  		return resp
   120  	}
   121  
   122  	return resp
   123  }
   124  
   125  // getSrc returns the file to use as source
   126  func getSrc(v cty.Value) (string, bool, error) {
   127  	content := v.GetAttr("content")
   128  	src := v.GetAttr("source")
   129  
   130  	switch {
   131  	case !content.IsNull():
   132  		file, err := ioutil.TempFile("", "tf-file-content")
   133  		if err != nil {
   134  			return "", true, err
   135  		}
   136  
   137  		if _, err = file.WriteString(content.AsString()); err != nil {
   138  			return "", true, err
   139  		}
   140  
   141  		return file.Name(), true, nil
   142  
   143  	case !src.IsNull():
   144  		expansion, err := homedir.Expand(src.AsString())
   145  		return expansion, false, err
   146  
   147  	default:
   148  		panic("source and content cannot both be null")
   149  	}
   150  }
   151  
   152  // copyFiles is used to copy the files from a source to a destination
   153  func copyFiles(ctx context.Context, comm communicator.Communicator, src, dst string) error {
   154  	retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
   155  	defer cancel()
   156  
   157  	// Wait and retry until we establish the connection
   158  	err := communicator.Retry(retryCtx, func() error {
   159  		return comm.Connect(nil)
   160  	})
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	// disconnect when the context is canceled, which will close this after
   166  	// Apply as well.
   167  	go func() {
   168  		<-ctx.Done()
   169  		comm.Disconnect()
   170  	}()
   171  
   172  	info, err := os.Stat(src)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	// If we're uploading a directory, short circuit and do that
   178  	if info.IsDir() {
   179  		if err := comm.UploadDir(dst, src); err != nil {
   180  			return fmt.Errorf("Upload failed: %v", err)
   181  		}
   182  		return nil
   183  	}
   184  
   185  	// We're uploading a file...
   186  	f, err := os.Open(src)
   187  	if err != nil {
   188  		return err
   189  	}
   190  	defer f.Close()
   191  
   192  	err = comm.Upload(dst, f)
   193  	if err != nil {
   194  		return fmt.Errorf("Upload failed: %v", err)
   195  	}
   196  
   197  	return err
   198  }
   199  
   200  func (p *provisioner) Stop() error {
   201  	p.cancel()
   202  	return nil
   203  }
   204  
   205  func (p *provisioner) Close() error {
   206  	return nil
   207  }