github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/builtin/provisioners/file/resource_provisioner.go (about) 1 package file 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 10 "github.com/hashicorp/terraform/internal/communicator" 11 "github.com/hashicorp/terraform/internal/configs/configschema" 12 "github.com/hashicorp/terraform/internal/provisioners" 13 "github.com/hashicorp/terraform/internal/tfdiags" 14 "github.com/mitchellh/go-homedir" 15 "github.com/zclconf/go-cty/cty" 16 ) 17 18 func New() provisioners.Interface { 19 ctx, cancel := context.WithCancel(context.Background()) 20 return &provisioner{ 21 ctx: ctx, 22 cancel: cancel, 23 } 24 } 25 26 type provisioner struct { 27 // We store a context here tied to the lifetime of the provisioner. 28 // This allows the Stop method to cancel any in-flight requests. 29 ctx context.Context 30 cancel context.CancelFunc 31 } 32 33 func (p *provisioner) GetSchema() (resp provisioners.GetSchemaResponse) { 34 schema := &configschema.Block{ 35 Attributes: map[string]*configschema.Attribute{ 36 "source": { 37 Type: cty.String, 38 Optional: true, 39 }, 40 41 "content": { 42 Type: cty.String, 43 Optional: true, 44 }, 45 46 "destination": { 47 Type: cty.String, 48 Required: true, 49 }, 50 }, 51 } 52 resp.Provisioner = schema 53 return resp 54 } 55 56 func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) { 57 cfg, err := p.GetSchema().Provisioner.CoerceValue(req.Config) 58 if err != nil { 59 resp.Diagnostics = resp.Diagnostics.Append(err) 60 } 61 62 source := cfg.GetAttr("source") 63 content := cfg.GetAttr("content") 64 65 switch { 66 case !source.IsNull() && !content.IsNull(): 67 resp.Diagnostics = resp.Diagnostics.Append(errors.New("Cannot set both 'source' and 'content'")) 68 return resp 69 case source.IsNull() && content.IsNull(): 70 resp.Diagnostics = resp.Diagnostics.Append(errors.New("Must provide one of 'source' or 'content'")) 71 return resp 72 } 73 74 return resp 75 } 76 77 func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 78 if req.Connection.IsNull() { 79 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody( 80 tfdiags.Error, 81 "file provisioner error", 82 "Missing connection configuration for provisioner.", 83 )) 84 return resp 85 } 86 87 comm, err := communicator.New(req.Connection) 88 if err != nil { 89 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody( 90 tfdiags.Error, 91 "file provisioner error", 92 err.Error(), 93 )) 94 return resp 95 } 96 97 // Get the source 98 src, deleteSource, err := getSrc(req.Config) 99 if err != nil { 100 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody( 101 tfdiags.Error, 102 "file provisioner error", 103 err.Error(), 104 )) 105 return resp 106 } 107 if deleteSource { 108 defer os.Remove(src) 109 } 110 111 // Begin the file copy 112 dst := req.Config.GetAttr("destination").AsString() 113 if err := copyFiles(p.ctx, comm, src, dst); err != nil { 114 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody( 115 tfdiags.Error, 116 "file provisioner error", 117 err.Error(), 118 )) 119 return resp 120 } 121 122 return resp 123 } 124 125 // getSrc returns the file to use as source 126 func getSrc(v cty.Value) (string, bool, error) { 127 content := v.GetAttr("content") 128 src := v.GetAttr("source") 129 130 switch { 131 case !content.IsNull(): 132 file, err := ioutil.TempFile("", "tf-file-content") 133 if err != nil { 134 return "", true, err 135 } 136 137 if _, err = file.WriteString(content.AsString()); err != nil { 138 return "", true, err 139 } 140 141 return file.Name(), true, nil 142 143 case !src.IsNull(): 144 expansion, err := homedir.Expand(src.AsString()) 145 return expansion, false, err 146 147 default: 148 panic("source and content cannot both be null") 149 } 150 } 151 152 // copyFiles is used to copy the files from a source to a destination 153 func copyFiles(ctx context.Context, comm communicator.Communicator, src, dst string) error { 154 retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout()) 155 defer cancel() 156 157 // Wait and retry until we establish the connection 158 err := communicator.Retry(retryCtx, func() error { 159 return comm.Connect(nil) 160 }) 161 if err != nil { 162 return err 163 } 164 165 // disconnect when the context is canceled, which will close this after 166 // Apply as well. 167 go func() { 168 <-ctx.Done() 169 comm.Disconnect() 170 }() 171 172 info, err := os.Stat(src) 173 if err != nil { 174 return err 175 } 176 177 // If we're uploading a directory, short circuit and do that 178 if info.IsDir() { 179 if err := comm.UploadDir(dst, src); err != nil { 180 return fmt.Errorf("Upload failed: %v", err) 181 } 182 return nil 183 } 184 185 // We're uploading a file... 186 f, err := os.Open(src) 187 if err != nil { 188 return err 189 } 190 defer f.Close() 191 192 err = comm.Upload(dst, f) 193 if err != nil { 194 return fmt.Errorf("Upload failed: %v", err) 195 } 196 197 return err 198 } 199 200 func (p *provisioner) Stop() error { 201 p.cancel() 202 return nil 203 } 204 205 func (p *provisioner) Close() error { 206 return nil 207 }