github.com/tompao/terraform@v0.6.10-0.20180215233341-e41b29d0961b/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 ctx, cancel := context.WithTimeout(ctx, comm.Timeout()) 52 defer cancel() 53 54 // Get the source 55 src, deleteSource, err := getSrc(data) 56 if err != nil { 57 return err 58 } 59 if deleteSource { 60 defer os.Remove(src) 61 } 62 63 // Begin the file copy 64 dst := data.Get("destination").(string) 65 66 if err := copyFiles(ctx, comm, src, dst); err != nil { 67 return err 68 } 69 return nil 70 } 71 72 func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) { 73 if !c.IsSet("source") && !c.IsSet("content") { 74 es = append(es, fmt.Errorf("Must provide one of 'source' or 'content'")) 75 } 76 77 return ws, es 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(ctx context.Context, comm communicator.Communicator, src, dst string) error { 102 // Wait and retry until we establish the connection 103 err := communicator.Retry(ctx, 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 }