github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/builtin/provisioners/file/resource_provisioner.go (about)

     1  package file
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"time"
    10  
    11  	"github.com/hashicorp/terraform/communicator"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/hashicorp/terraform/terraform"
    14  	"github.com/mitchellh/go-homedir"
    15  )
    16  
    17  func Provisioner() terraform.ResourceProvisioner {
    18  	return &schema.Provisioner{
    19  		Schema: map[string]*schema.Schema{
    20  			"source": &schema.Schema{
    21  				Type:          schema.TypeString,
    22  				Optional:      true,
    23  				ConflictsWith: []string{"content"},
    24  			},
    25  
    26  			"content": &schema.Schema{
    27  				Type:          schema.TypeString,
    28  				Optional:      true,
    29  				ConflictsWith: []string{"source"},
    30  			},
    31  
    32  			"destination": &schema.Schema{
    33  				Type:     schema.TypeString,
    34  				Required: true,
    35  			},
    36  		},
    37  
    38  		ApplyFunc:    applyFn,
    39  		ValidateFunc: validateFn,
    40  	}
    41  }
    42  
    43  func applyFn(ctx context.Context) error {
    44  	connState := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
    45  	data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
    46  
    47  	// Get a new communicator
    48  	comm, err := communicator.New(connState)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	// Get the source
    54  	src, deleteSource, err := getSrc(data)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	if deleteSource {
    59  		defer os.Remove(src)
    60  	}
    61  
    62  	// Begin the file copy
    63  	dst := data.Get("destination").(string)
    64  	resultCh := make(chan error, 1)
    65  	go func() {
    66  		resultCh <- copyFiles(comm, src, dst)
    67  	}()
    68  
    69  	// Allow the file copy to complete unless there is an interrupt.
    70  	// If there is an interrupt we make no attempt to cleanly close
    71  	// the connection currently. We just abruptly exit. Because Terraform
    72  	// taints the resource, this is fine.
    73  	select {
    74  	case err := <-resultCh:
    75  		return err
    76  	case <-ctx.Done():
    77  		return fmt.Errorf("file transfer interrupted")
    78  	}
    79  }
    80  
    81  func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
    82  	if !c.IsSet("source") && !c.IsSet("content") {
    83  		es = append(es, fmt.Errorf("Must provide one of 'source' or 'content'"))
    84  	}
    85  
    86  	return ws, es
    87  }
    88  
    89  // getSrc returns the file to use as source
    90  func getSrc(data *schema.ResourceData) (string, bool, error) {
    91  	src := data.Get("source").(string)
    92  	if content, ok := data.GetOk("content"); ok {
    93  		file, err := ioutil.TempFile("", "tf-file-content")
    94  		if err != nil {
    95  			return "", true, err
    96  		}
    97  
    98  		if _, err = file.WriteString(content.(string)); err != nil {
    99  			return "", true, err
   100  		}
   101  
   102  		return file.Name(), true, nil
   103  	}
   104  
   105  	expansion, err := homedir.Expand(src)
   106  	return expansion, false, err
   107  }
   108  
   109  // copyFiles is used to copy the files from a source to a destination
   110  func copyFiles(comm communicator.Communicator, src, dst string) error {
   111  	// Wait and retry until we establish the connection
   112  	err := retryFunc(comm.Timeout(), func() error {
   113  		err := comm.Connect(nil)
   114  		return err
   115  	})
   116  	if err != nil {
   117  		return err
   118  	}
   119  	defer comm.Disconnect()
   120  
   121  	info, err := os.Stat(src)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	// If we're uploading a directory, short circuit and do that
   127  	if info.IsDir() {
   128  		if err := comm.UploadDir(dst, src); err != nil {
   129  			return fmt.Errorf("Upload failed: %v", err)
   130  		}
   131  		return nil
   132  	}
   133  
   134  	// We're uploading a file...
   135  	f, err := os.Open(src)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	defer f.Close()
   140  
   141  	err = comm.Upload(dst, f)
   142  	if err != nil {
   143  		return fmt.Errorf("Upload failed: %v", err)
   144  	}
   145  	return err
   146  }
   147  
   148  // retryFunc is used to retry a function for a given duration
   149  func retryFunc(timeout time.Duration, f func() error) error {
   150  	finish := time.After(timeout)
   151  	for {
   152  		err := f()
   153  		if err == nil {
   154  			return nil
   155  		}
   156  		log.Printf("Retryable error: %v", err)
   157  
   158  		select {
   159  		case <-finish:
   160  			return err
   161  		case <-time.After(3 * time.Second):
   162  		}
   163  	}
   164  }