github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 }