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