diff --git a/builtin/provisioners/file/resource_provisioner.go b/builtin/provisioners/file/resource_provisioner.go index 2cd060b63..012fc768d 100644 --- a/builtin/provisioners/file/resource_provisioner.go +++ b/builtin/provisioners/file/resource_provisioner.go @@ -1,6 +1,7 @@ package file import ( + "context" "fmt" "io/ioutil" "log" @@ -8,26 +9,48 @@ import ( "time" "github.com/hashicorp/terraform/communicator" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/go-homedir" ) -// ResourceProvisioner represents a file provisioner -type ResourceProvisioner struct{} +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, + } +} + +func applyFn(ctx context.Context) error { + connData := ctx.Value(schema.ProvConnDataKey).(*schema.ResourceData) + data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData) -// Apply executes the file provisioner -func (p *ResourceProvisioner) Apply( - o terraform.UIOutput, - s *terraform.InstanceState, - c *terraform.ResourceConfig) error { // Get a new communicator - comm, err := communicator.New(s) + comm, err := communicator.NewData(connData) if err != nil { return err } // Get the source - src, deleteSource, err := p.getSrc(c) + src, deleteSource, err := getSrc(data) if err != nil { return err } @@ -36,57 +59,20 @@ func (p *ResourceProvisioner) Apply( } // Get destination - dRaw := c.Config["destination"] - dst, ok := dRaw.(string) - if !ok { - return fmt.Errorf("Unsupported 'destination' type! Must be string.") - } - return p.copyFiles(comm, src, dst) -} - -// Validate checks if the required arguments are configured -func (p *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string, es []error) { - numDst := 0 - numSrc := 0 - for name := range c.Raw { - switch name { - case "destination": - numDst++ - case "source", "content": - numSrc++ - default: - es = append(es, fmt.Errorf("Unknown configuration '%s'", name)) - } - } - if numSrc != 1 || numDst != 1 { - es = append(es, fmt.Errorf("Must provide one of 'content' or 'source' and 'destination' to file")) - } - return + dst := data.Get("destination").(string) + return copyFiles(comm, src, dst) } // getSrc returns the file to use as source -func (p *ResourceProvisioner) getSrc(c *terraform.ResourceConfig) (string, bool, error) { - var src string - - sRaw, ok := c.Config["source"] - if ok { - if src, ok = sRaw.(string); !ok { - return "", false, fmt.Errorf("Unsupported 'source' type! Must be string.") - } - } - - content, ok := c.Config["content"] - if ok { +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 } - contentStr, ok := content.(string) - if !ok { - return "", true, fmt.Errorf("Unsupported 'content' type! Must be string.") - } - if _, err = file.WriteString(contentStr); err != nil { + if _, err = file.WriteString(content.(string)); err != nil { return "", true, err } @@ -98,7 +84,7 @@ func (p *ResourceProvisioner) getSrc(c *terraform.ResourceConfig) (string, bool, } // copyFiles is used to copy the files from a source to a destination -func (p *ResourceProvisioner) copyFiles(comm communicator.Communicator, src, dst string) error { +func copyFiles(comm communicator.Communicator, src, dst string) error { // Wait and retry until we establish the connection err := retryFunc(comm.Timeout(), func() error { err := comm.Connect(nil) diff --git a/builtin/provisioners/file/resource_provisioner_test.go b/builtin/provisioners/file/resource_provisioner_test.go index 713c8aa0d..d57dbbaa5 100644 --- a/builtin/provisioners/file/resource_provisioner_test.go +++ b/builtin/provisioners/file/resource_provisioner_test.go @@ -7,16 +7,12 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestResourceProvisioner_impl(t *testing.T) { - var _ terraform.ResourceProvisioner = new(ResourceProvisioner) -} - func TestResourceProvider_Validate_good_source(t *testing.T) { c := testConfig(t, map[string]interface{}{ "source": "/tmp/foo", "destination": "/tmp/bar", }) - p := new(ResourceProvisioner) + p := Provisioner() warn, errs := p.Validate(c) if len(warn) > 0 { t.Fatalf("Warnings: %v", warn) @@ -31,7 +27,7 @@ func TestResourceProvider_Validate_good_content(t *testing.T) { "content": "value to copy", "destination": "/tmp/bar", }) - p := new(ResourceProvisioner) + p := Provisioner() warn, errs := p.Validate(c) if len(warn) > 0 { t.Fatalf("Warnings: %v", warn) @@ -45,7 +41,7 @@ func TestResourceProvider_Validate_bad_not_destination(t *testing.T) { c := testConfig(t, map[string]interface{}{ "source": "nope", }) - p := new(ResourceProvisioner) + p := Provisioner() warn, errs := p.Validate(c) if len(warn) > 0 { t.Fatalf("Warnings: %v", warn) @@ -61,7 +57,7 @@ func TestResourceProvider_Validate_bad_to_many_src(t *testing.T) { "content": "value to copy", "destination": "/tmp/bar", }) - p := new(ResourceProvisioner) + p := Provisioner() warn, errs := p.Validate(c) if len(warn) > 0 { t.Fatalf("Warnings: %v", warn) diff --git a/communicator/communicator.go b/communicator/communicator.go index 5fa2631a4..21f7b6fea 100644 --- a/communicator/communicator.go +++ b/communicator/communicator.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/communicator/remote" "github.com/hashicorp/terraform/communicator/ssh" "github.com/hashicorp/terraform/communicator/winrm" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) @@ -51,3 +52,18 @@ func New(s *terraform.InstanceState) (Communicator, error) { return nil, fmt.Errorf("connection type '%s' not supported", connType) } } + +// NewData creates a new Communicator from a ResourceData structure that +// represents the connection information. +func NewData(d *schema.ResourceData) (Communicator, error) { + // Turn the ResourceData into a legacy-style ConnInfo struct that + // is used to instantiate the communicator. + raw := d.State() + state := &terraform.InstanceState{ + Ephemeral: terraform.EphemeralState{ + ConnInfo: raw.Attributes, + }, + } + + return New(state) +}