github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/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  	}
    40  }
    41  
    42  func applyFn(ctx context.Context) error {
    43  	connState := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
    44  	data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
    45  
    46  	// Get a new communicator
    47  	comm, err := communicator.New(connState)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	// Get the source
    53  	src, deleteSource, err := getSrc(data)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	if deleteSource {
    58  		defer os.Remove(src)
    59  	}
    60  
    61  	// Begin the file copy
    62  	dst := data.Get("destination").(string)
    63  	resultCh := make(chan error, 1)
    64  	go func() {
    65  		resultCh <- copyFiles(comm, src, dst)
    66  	}()
    67  
    68  	// Allow the file copy to complete unless there is an interrupt.
    69  	// If there is an interrupt we make no attempt to cleanly close
    70  	// the connection currently. We just abruptly exit. Because Terraform
    71  	// taints the resource, this is fine.
    72  	select {
    73  	case err := <-resultCh:
    74  		return err
    75  	case <-ctx.Done():
    76  		return fmt.Errorf("file transfer interrupted")
    77  	}
    78  }
    79  
    80  // getSrc returns the file to use as source
    81  func getSrc(data *schema.ResourceData) (string, bool, error) {
    82  	src := data.Get("source").(string)
    83  	if content, ok := data.GetOk("content"); ok {
    84  		file, err := ioutil.TempFile("", "tf-file-content")
    85  		if err != nil {
    86  			return "", true, err
    87  		}
    88  
    89  		if _, err = file.WriteString(content.(string)); err != nil {
    90  			return "", true, err
    91  		}
    92  
    93  		return file.Name(), true, nil
    94  	}
    95  
    96  	expansion, err := homedir.Expand(src)
    97  	return expansion, false, err
    98  }
    99  
   100  // copyFiles is used to copy the files from a source to a destination
   101  func copyFiles(comm communicator.Communicator, src, dst string) error {
   102  	// Wait and retry until we establish the connection
   103  	err := retryFunc(comm.Timeout(), func() error {
   104  		err := comm.Connect(nil)
   105  		return err
   106  	})
   107  	if err != nil {
   108  		return err
   109  	}
   110  	defer comm.Disconnect()
   111  
   112  	info, err := os.Stat(src)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	// If we're uploading a directory, short circuit and do that
   118  	if info.IsDir() {
   119  		if err := comm.UploadDir(dst, src); err != nil {
   120  			return fmt.Errorf("Upload failed: %v", err)
   121  		}
   122  		return nil
   123  	}
   124  
   125  	// We're uploading a file...
   126  	f, err := os.Open(src)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	defer f.Close()
   131  
   132  	err = comm.Upload(dst, f)
   133  	if err != nil {
   134  		return fmt.Errorf("Upload failed: %v", err)
   135  	}
   136  	return err
   137  }
   138  
   139  // retryFunc is used to retry a function for a given duration
   140  func retryFunc(timeout time.Duration, f func() error) error {
   141  	finish := time.After(timeout)
   142  	for {
   143  		err := f()
   144  		if err == nil {
   145  			return nil
   146  		}
   147  		log.Printf("Retryable error: %v", err)
   148  
   149  		select {
   150  		case <-finish:
   151  			return err
   152  		case <-time.After(3 * time.Second):
   153  		}
   154  	}
   155  }