From 272ffcbe44edff3ae74a432bd4fde20ec01f0613 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Tue, 15 Jul 2014 16:52:00 -0700 Subject: [PATCH] provisioner/file: Initial pass at file provisioner --- .../provisioners/file/resource_provisioner.go | 105 +++++++++++++++++- 1 file changed, 102 insertions(+), 3 deletions(-) diff --git a/builtin/provisioners/file/resource_provisioner.go b/builtin/provisioners/file/resource_provisioner.go index 7f6b4a005d..9eae86481d 100644 --- a/builtin/provisioners/file/resource_provisioner.go +++ b/builtin/provisioners/file/resource_provisioner.go @@ -1,6 +1,13 @@ package file import ( + "fmt" + "log" + "os" + "time" + + "github.com/hashicorp/terraform/helper/config" + helper "github.com/hashicorp/terraform/helper/ssh" "github.com/hashicorp/terraform/terraform" ) @@ -8,10 +15,102 @@ type ResourceProvisioner struct{} func (p *ResourceProvisioner) Apply(s *terraform.ResourceState, c *terraform.ResourceConfig) (*terraform.ResourceState, error) { - panic("not implemented") - return s, nil + // Ensure the connection type is SSH + if err := helper.VerifySSH(s); err != nil { + return s, err + } + + // Get the SSH configuration + conf, err := helper.ParseSSHConfig(s) + if err != nil { + return s, err + } + + // Get the source and destination + sRaw := c.Config["source"] + src, ok := sRaw.(string) + if !ok { + return s, fmt.Errorf("Unsupported 'source' type! Must be string.") + } + + dRaw := c.Config["destination"] + dst, ok := dRaw.(string) + if !ok { + return s, fmt.Errorf("Unsupported 'destination' type! Must be string.") + } + return s, p.copyFiles(conf, src, dst) } func (p *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string, es []error) { - return + v := &config.Validator{ + Required: []string{ + "source", + "destination", + }, + } + return v.Validate(c) +} + +// copyFiles is used to copy the files from a source to a destination +func (p *ResourceProvisioner) copyFiles(conf *helper.SSHConfig, src, dst string) error { + // Get the SSH client config + config, err := helper.PrepareConfig(conf) + if err != nil { + return err + } + + // Wait and retry until we establish the SSH connection + var comm *helper.SSHCommunicator + err = retryFunc(conf.TimeoutVal, func() error { + host := fmt.Sprintf("%s:%d", conf.Host, conf.Port) + comm, err = helper.New(host, config) + return err + }) + if err != nil { + return err + } + + 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, nil); 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 +} + +// retryFunc is used to retry a function for a given duration +func retryFunc(timeout time.Duration, f func() error) error { + finish := time.After(timeout) + for { + err := f() + if err == nil { + return nil + } + log.Printf("Retryable error: %v", err) + + select { + case <-finish: + return err + case <-time.After(3 * time.Second): + } + } }