github.com/daveadams/terraform@v0.6.4-0.20160830094355-13ce74975936/builtin/provisioners/file/resource_provisioner.go (about)

     1  package file
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"log"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform/communicator"
    11  	"github.com/hashicorp/terraform/terraform"
    12  	"github.com/mitchellh/go-homedir"
    13  )
    14  
    15  // ResourceProvisioner represents a file provisioner
    16  type ResourceProvisioner struct{}
    17  
    18  // Apply executes the file provisioner
    19  func (p *ResourceProvisioner) Apply(
    20  	o terraform.UIOutput,
    21  	s *terraform.InstanceState,
    22  	c *terraform.ResourceConfig) error {
    23  	// Get a new communicator
    24  	comm, err := communicator.New(s)
    25  	if err != nil {
    26  		return err
    27  	}
    28  
    29  	// Get the source
    30  	src, deleteSource, err := p.getSrc(c)
    31  	if err != nil {
    32  		return err
    33  	}
    34  	if deleteSource {
    35  		defer os.Remove(src)
    36  	}
    37  
    38  	// Get destination
    39  	dRaw := c.Config["destination"]
    40  	dst, ok := dRaw.(string)
    41  	if !ok {
    42  		return fmt.Errorf("Unsupported 'destination' type! Must be string.")
    43  	}
    44  	return p.copyFiles(comm, src, dst)
    45  }
    46  
    47  // Validate checks if the required arguments are configured
    48  func (p *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string, es []error) {
    49  	numDst := 0
    50  	numSrc := 0
    51  	for name := range c.Raw {
    52  		switch name {
    53  		case "destination":
    54  			numDst++
    55  		case "source", "content":
    56  			numSrc++
    57  		default:
    58  			es = append(es, fmt.Errorf("Unknown configuration '%s'", name))
    59  		}
    60  	}
    61  	if numSrc != 1 || numDst != 1 {
    62  		es = append(es, fmt.Errorf("Must provide one  of 'content' or 'source' and 'destination' to file"))
    63  	}
    64  	return
    65  }
    66  
    67  // getSrc returns the file to use as source
    68  func (p *ResourceProvisioner) getSrc(c *terraform.ResourceConfig) (string, bool, error) {
    69  	var src string
    70  
    71  	sRaw, ok := c.Config["source"]
    72  	if ok {
    73  		if src, ok = sRaw.(string); !ok {
    74  			return "", false, fmt.Errorf("Unsupported 'source' type! Must be string.")
    75  		}
    76  	}
    77  
    78  	content, ok := c.Config["content"]
    79  	if ok {
    80  		file, err := ioutil.TempFile("", "tf-file-content")
    81  		if err != nil {
    82  			return "", true, err
    83  		}
    84  
    85  		contentStr, ok := content.(string)
    86  		if !ok {
    87  			return "", true, fmt.Errorf("Unsupported 'content' type! Must be string.")
    88  		}
    89  		if _, err = file.WriteString(contentStr); 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 (p *ResourceProvisioner) 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  }