github.com/lymingtonprecision/terraform@v0.9.9-0.20170613092852-62acef9611a9/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(d *schema.ResourceData) (ws []string, es []error) {
    82  	numSrc := 0
    83  	if _, ok := d.GetOk("source"); ok == true {
    84  		numSrc++
    85  	}
    86  	if _, ok := d.GetOk("content"); ok == true {
    87  		numSrc++
    88  	}
    89  	if numSrc != 1 {
    90  		es = append(es, fmt.Errorf("Must provide one of 'content' or 'source'"))
    91  	}
    92  	return
    93  }
    94  
    95  // getSrc returns the file to use as source
    96  func getSrc(data *schema.ResourceData) (string, bool, error) {
    97  	src := data.Get("source").(string)
    98  	if content, ok := data.GetOk("content"); ok {
    99  		file, err := ioutil.TempFile("", "tf-file-content")
   100  		if err != nil {
   101  			return "", true, err
   102  		}
   103  
   104  		if _, err = file.WriteString(content.(string)); err != nil {
   105  			return "", true, err
   106  		}
   107  
   108  		return file.Name(), true, nil
   109  	}
   110  
   111  	expansion, err := homedir.Expand(src)
   112  	return expansion, false, err
   113  }
   114  
   115  // copyFiles is used to copy the files from a source to a destination
   116  func copyFiles(comm communicator.Communicator, src, dst string) error {
   117  	// Wait and retry until we establish the connection
   118  	err := retryFunc(comm.Timeout(), func() error {
   119  		err := comm.Connect(nil)
   120  		return err
   121  	})
   122  	if err != nil {
   123  		return err
   124  	}
   125  	defer comm.Disconnect()
   126  
   127  	info, err := os.Stat(src)
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	// If we're uploading a directory, short circuit and do that
   133  	if info.IsDir() {
   134  		if err := comm.UploadDir(dst, src); err != nil {
   135  			return fmt.Errorf("Upload failed: %v", err)
   136  		}
   137  		return nil
   138  	}
   139  
   140  	// We're uploading a file...
   141  	f, err := os.Open(src)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	defer f.Close()
   146  
   147  	err = comm.Upload(dst, f)
   148  	if err != nil {
   149  		return fmt.Errorf("Upload failed: %v", err)
   150  	}
   151  	return err
   152  }
   153  
   154  // retryFunc is used to retry a function for a given duration
   155  func retryFunc(timeout time.Duration, f func() error) error {
   156  	finish := time.After(timeout)
   157  	for {
   158  		err := f()
   159  		if err == nil {
   160  			return nil
   161  		}
   162  		log.Printf("Retryable error: %v", err)
   163  
   164  		select {
   165  		case <-finish:
   166  			return err
   167  		case <-time.After(3 * time.Second):
   168  		}
   169  	}
   170  }