github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/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 }