github.com/Hashicorp/terraform@v0.11.12-beta1/builtin/provisioners/file/resource_provisioner.go (about) 1 package file 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "os" 8 9 "github.com/hashicorp/terraform/communicator" 10 "github.com/hashicorp/terraform/helper/schema" 11 "github.com/hashicorp/terraform/terraform" 12 "github.com/mitchellh/go-homedir" 13 ) 14 15 func Provisioner() terraform.ResourceProvisioner { 16 return &schema.Provisioner{ 17 Schema: map[string]*schema.Schema{ 18 "source": &schema.Schema{ 19 Type: schema.TypeString, 20 Optional: true, 21 ConflictsWith: []string{"content"}, 22 }, 23 24 "content": &schema.Schema{ 25 Type: schema.TypeString, 26 Optional: true, 27 ConflictsWith: []string{"source"}, 28 }, 29 30 "destination": &schema.Schema{ 31 Type: schema.TypeString, 32 Required: true, 33 }, 34 }, 35 36 ApplyFunc: applyFn, 37 ValidateFunc: validateFn, 38 } 39 } 40 41 func applyFn(ctx context.Context) error { 42 connState := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState) 43 data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData) 44 45 // Get a new communicator 46 comm, err := communicator.New(connState) 47 if err != nil { 48 return err 49 } 50 51 // Get the source 52 src, deleteSource, err := getSrc(data) 53 if err != nil { 54 return err 55 } 56 if deleteSource { 57 defer os.Remove(src) 58 } 59 60 // Begin the file copy 61 dst := data.Get("destination").(string) 62 63 if err := copyFiles(ctx, comm, src, dst); err != nil { 64 return err 65 } 66 return nil 67 } 68 69 func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) { 70 if !c.IsSet("source") && !c.IsSet("content") { 71 es = append(es, fmt.Errorf("Must provide one of 'source' or 'content'")) 72 } 73 74 return ws, es 75 } 76 77 // getSrc returns the file to use as source 78 func getSrc(data *schema.ResourceData) (string, bool, error) { 79 src := data.Get("source").(string) 80 if content, ok := data.GetOk("content"); ok { 81 file, err := ioutil.TempFile("", "tf-file-content") 82 if err != nil { 83 return "", true, err 84 } 85 86 if _, err = file.WriteString(content.(string)); err != nil { 87 return "", true, err 88 } 89 90 return file.Name(), true, nil 91 } 92 93 expansion, err := homedir.Expand(src) 94 return expansion, false, err 95 } 96 97 // copyFiles is used to copy the files from a source to a destination 98 func copyFiles(ctx context.Context, comm communicator.Communicator, src, dst string) error { 99 retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout()) 100 defer cancel() 101 102 // Wait and retry until we establish the connection 103 err := communicator.Retry(retryCtx, func() error { 104 return comm.Connect(nil) 105 }) 106 if err != nil { 107 return err 108 } 109 110 // disconnect when the context is canceled, which will close this after 111 // Apply as well. 112 go func() { 113 <-ctx.Done() 114 comm.Disconnect() 115 }() 116 117 info, err := os.Stat(src) 118 if err != nil { 119 return err 120 } 121 122 // If we're uploading a directory, short circuit and do that 123 if info.IsDir() { 124 if err := comm.UploadDir(dst, src); err != nil { 125 return fmt.Errorf("Upload failed: %v", err) 126 } 127 return nil 128 } 129 130 // We're uploading a file... 131 f, err := os.Open(src) 132 if err != nil { 133 return err 134 } 135 defer f.Close() 136 137 err = comm.Upload(dst, f) 138 if err != nil { 139 return fmt.Errorf("Upload failed: %v", err) 140 } 141 return err 142 }