mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-18 20:52:58 -06:00
9b4b5f2a72
The timeout for a provisioner is expected to only apply to the initial connection. Keep the context for the communicator.Retry separate from the global cancellation context.
143 lines
3.0 KiB
Go
143 lines
3.0 KiB
Go
package file
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"github.com/hashicorp/terraform/communicator"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
"github.com/mitchellh/go-homedir"
|
|
)
|
|
|
|
func Provisioner() terraform.ResourceProvisioner {
|
|
return &schema.Provisioner{
|
|
Schema: map[string]*schema.Schema{
|
|
"source": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ConflictsWith: []string{"content"},
|
|
},
|
|
|
|
"content": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ConflictsWith: []string{"source"},
|
|
},
|
|
|
|
"destination": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
},
|
|
|
|
ApplyFunc: applyFn,
|
|
ValidateFunc: validateFn,
|
|
}
|
|
}
|
|
|
|
func applyFn(ctx context.Context) error {
|
|
connState := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
|
|
data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
|
|
|
|
// Get a new communicator
|
|
comm, err := communicator.New(connState)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get the source
|
|
src, deleteSource, err := getSrc(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if deleteSource {
|
|
defer os.Remove(src)
|
|
}
|
|
|
|
// Begin the file copy
|
|
dst := data.Get("destination").(string)
|
|
|
|
if err := copyFiles(ctx, comm, src, dst); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
|
|
if !c.IsSet("source") && !c.IsSet("content") {
|
|
es = append(es, fmt.Errorf("Must provide one of 'source' or 'content'"))
|
|
}
|
|
|
|
return ws, es
|
|
}
|
|
|
|
// getSrc returns the file to use as source
|
|
func getSrc(data *schema.ResourceData) (string, bool, error) {
|
|
src := data.Get("source").(string)
|
|
if content, ok := data.GetOk("content"); ok {
|
|
file, err := ioutil.TempFile("", "tf-file-content")
|
|
if err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
if _, err = file.WriteString(content.(string)); err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
return file.Name(), true, nil
|
|
}
|
|
|
|
expansion, err := homedir.Expand(src)
|
|
return expansion, false, err
|
|
}
|
|
|
|
// copyFiles is used to copy the files from a source to a destination
|
|
func copyFiles(ctx context.Context, comm communicator.Communicator, src, dst string) error {
|
|
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
|
defer cancel()
|
|
|
|
// Wait and retry until we establish the connection
|
|
err := communicator.Retry(retryCtx, func() error {
|
|
return comm.Connect(nil)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// disconnect when the context is canceled, which will close this after
|
|
// Apply as well.
|
|
go func() {
|
|
<-ctx.Done()
|
|
comm.Disconnect()
|
|
}()
|
|
|
|
info, err := os.Stat(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If we're uploading a directory, short circuit and do that
|
|
if info.IsDir() {
|
|
if err := comm.UploadDir(dst, src); err != nil {
|
|
return fmt.Errorf("Upload failed: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// We're uploading a file...
|
|
f, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
err = comm.Upload(dst, f)
|
|
if err != nil {
|
|
return fmt.Errorf("Upload failed: %v", err)
|
|
}
|
|
return err
|
|
}
|