From 3c0c81957a5064ed8d8592b29ffdc68be563206d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 22 Dec 2016 17:05:45 -0800 Subject: [PATCH] provisioners/remote-exec: switch to helper/schema --- .../remote-exec/resource_provisioner.go | 132 +++++++----------- .../remote-exec/resource_provisioner_test.go | 47 ++++--- helper/schema/testing.go | 30 ++++ 3 files changed, 108 insertions(+), 101 deletions(-) create mode 100644 helper/schema/testing.go diff --git a/builtin/provisioners/remote-exec/resource_provisioner.go b/builtin/provisioners/remote-exec/resource_provisioner.go index 46d4fd1ac..43ab12b0a 100644 --- a/builtin/provisioners/remote-exec/resource_provisioner.go +++ b/builtin/provisioners/remote-exec/resource_provisioner.go @@ -2,6 +2,7 @@ package remoteexec import ( "bytes" + "context" "fmt" "io" "io/ioutil" @@ -11,26 +12,53 @@ import ( "github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator/remote" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/go-linereader" ) -// ResourceProvisioner represents a remote exec provisioner -type ResourceProvisioner struct{} +func Provisioner() terraform.ResourceProvisioner { + return &schema.Provisioner{ + Schema: map[string]*schema.Schema{ + "inline": &schema.Schema{ + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + ConflictsWith: []string{"script", "scripts"}, + }, + + "script": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"inline", "scripts"}, + }, + + "scripts": &schema.Schema{ + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + ConflictsWith: []string{"script", "inline"}, + }, + }, + + ApplyFunc: applyFn, + } +} // Apply executes the remote exec provisioner -func (p *ResourceProvisioner) Apply( - o terraform.UIOutput, - s *terraform.InstanceState, - c *terraform.ResourceConfig) error { +func applyFn(ctx context.Context) error { + connState := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState) + data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData) + o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput) + // Get a new communicator - comm, err := communicator.New(s) + comm, err := communicator.New(connState) if err != nil { return err } // Collect the scripts - scripts, err := p.collectScripts(c) + scripts, err := collectScripts(data) if err != nil { return err } @@ -39,67 +67,33 @@ func (p *ResourceProvisioner) Apply( } // Copy and execute each script - if err := p.runScripts(o, comm, scripts); err != nil { + if err := runScripts(o, comm, scripts); err != nil { return err } + return nil } -// Validate checks if the required arguments are configured -func (p *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string, es []error) { - num := 0 - for name := range c.Raw { - switch name { - case "scripts", "script", "inline": - num++ - default: - es = append(es, fmt.Errorf("Unknown configuration '%s'", name)) - } - } - if num != 1 { - es = append(es, fmt.Errorf("Must provide one of 'scripts', 'script' or 'inline' to remote-exec")) - } - return -} - // generateScripts takes the configuration and creates a script from each inline config -func (p *ResourceProvisioner) generateScripts(c *terraform.ResourceConfig) ([]string, error) { +func generateScripts(d *schema.ResourceData) ([]string, error) { var scripts []string - command, ok := c.Config["inline"] - if ok { - switch cmd := command.(type) { - case string: - scripts = append(scripts, cmd) - case []string: - scripts = append(scripts, cmd...) - case []interface{}: - for _, l := range cmd { - lStr, ok := l.(string) - if ok { - scripts = append(scripts, lStr) - } else { - return nil, fmt.Errorf("Unsupported 'inline' type! Must be string, or list of strings.") - } - } - default: - return nil, fmt.Errorf("Unsupported 'inline' type! Must be string, or list of strings.") - } + for _, l := range d.Get("inline").([]interface{}) { + scripts = append(scripts, l.(string)) } return scripts, nil } // collectScripts is used to collect all the scripts we need // to execute in preparation for copying them. -func (p *ResourceProvisioner) collectScripts(c *terraform.ResourceConfig) ([]io.ReadCloser, error) { +func collectScripts(d *schema.ResourceData) ([]io.ReadCloser, error) { // Check if inline - _, ok := c.Config["inline"] - if ok { - scripts, err := p.generateScripts(c) + if _, ok := d.GetOk("inline"); ok { + scripts, err := generateScripts(d) if err != nil { return nil, err } - r := []io.ReadCloser{} + var r []io.ReadCloser for _, script := range scripts { r = append(r, ioutil.NopCloser(bytes.NewReader([]byte(script)))) } @@ -109,31 +103,13 @@ func (p *ResourceProvisioner) collectScripts(c *terraform.ResourceConfig) ([]io. // Collect scripts var scripts []string - s, ok := c.Config["script"] - if ok { - sStr, ok := s.(string) - if !ok { - return nil, fmt.Errorf("Unsupported 'script' type! Must be a string.") - } - scripts = append(scripts, sStr) + if script, ok := d.GetOk("script"); ok { + scripts = append(scripts, script.(string)) } - sl, ok := c.Config["scripts"] - if ok { - switch slt := sl.(type) { - case []string: - scripts = append(scripts, slt...) - case []interface{}: - for _, l := range slt { - lStr, ok := l.(string) - if ok { - scripts = append(scripts, lStr) - } else { - return nil, fmt.Errorf("Unsupported 'scripts' type! Must be list of strings.") - } - } - default: - return nil, fmt.Errorf("Unsupported 'scripts' type! Must be list of strings.") + if scriptList, ok := d.GetOk("scripts"); ok { + for _, script := range scriptList.([]interface{}) { + scripts = append(scripts, script.(string)) } } @@ -155,7 +131,7 @@ func (p *ResourceProvisioner) collectScripts(c *terraform.ResourceConfig) ([]io. } // runScripts is used to copy and execute a set of scripts -func (p *ResourceProvisioner) runScripts( +func runScripts( o terraform.UIOutput, comm communicator.Communicator, scripts []io.ReadCloser) error { @@ -175,8 +151,8 @@ func (p *ResourceProvisioner) runScripts( errR, errW := io.Pipe() outDoneCh := make(chan struct{}) errDoneCh := make(chan struct{}) - go p.copyOutput(o, outR, outDoneCh) - go p.copyOutput(o, errR, errDoneCh) + go copyOutput(o, outR, outDoneCh) + go copyOutput(o, errR, errDoneCh) remotePath := comm.ScriptPath() err = retryFunc(comm.Timeout(), func() error { @@ -225,7 +201,7 @@ func (p *ResourceProvisioner) runScripts( return nil } -func (p *ResourceProvisioner) copyOutput( +func copyOutput( o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) { defer close(doneCh) lr := linereader.New(r) diff --git a/builtin/provisioners/remote-exec/resource_provisioner_test.go b/builtin/provisioners/remote-exec/resource_provisioner_test.go index a581301e3..5508e58dc 100644 --- a/builtin/provisioners/remote-exec/resource_provisioner_test.go +++ b/builtin/provisioners/remote-exec/resource_provisioner_test.go @@ -9,18 +9,15 @@ import ( "reflect" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) -func TestResourceProvisioner_impl(t *testing.T) { - var _ terraform.ResourceProvisioner = new(ResourceProvisioner) -} - func TestResourceProvider_Validate_good(t *testing.T) { c := testConfig(t, map[string]interface{}{ "inline": "echo foo", }) - p := new(ResourceProvisioner) + p := Provisioner() warn, errs := p.Validate(c) if len(warn) > 0 { t.Fatalf("Warnings: %v", warn) @@ -34,7 +31,7 @@ func TestResourceProvider_Validate_bad(t *testing.T) { c := testConfig(t, map[string]interface{}{ "invalid": "nope", }) - p := new(ResourceProvisioner) + p := Provisioner() warn, errs := p.Validate(c) if len(warn) > 0 { t.Fatalf("Warnings: %v", warn) @@ -51,16 +48,17 @@ exit 0 var expectedInlineScriptsOut = strings.Split(expectedScriptOut, "\n") -func TestResourceProvider_generateScripts(t *testing.T) { - p := new(ResourceProvisioner) - conf := testConfig(t, map[string]interface{}{ +func TestResourceProvider_generateScript(t *testing.T) { + p := Provisioner().(*schema.Provisioner) + conf := map[string]interface{}{ "inline": []interface{}{ "cd /tmp", "wget http://foobar", "exit 0", }, - }) - out, err := p.generateScripts(conf) + } + out, err := generateScripts(schema.TestResourceDataRaw( + t, p.Schema, conf)) if err != nil { t.Fatalf("err: %v", err) } @@ -71,16 +69,17 @@ func TestResourceProvider_generateScripts(t *testing.T) { } func TestResourceProvider_CollectScripts_inline(t *testing.T) { - p := new(ResourceProvisioner) - conf := testConfig(t, map[string]interface{}{ + p := Provisioner().(*schema.Provisioner) + conf := map[string]interface{}{ "inline": []interface{}{ "cd /tmp", "wget http://foobar", "exit 0", }, - }) + } - scripts, err := p.collectScripts(conf) + scripts, err := collectScripts(schema.TestResourceDataRaw( + t, p.Schema, conf)) if err != nil { t.Fatalf("err: %v", err) } @@ -103,12 +102,13 @@ func TestResourceProvider_CollectScripts_inline(t *testing.T) { } func TestResourceProvider_CollectScripts_script(t *testing.T) { - p := new(ResourceProvisioner) - conf := testConfig(t, map[string]interface{}{ + p := Provisioner().(*schema.Provisioner) + conf := map[string]interface{}{ "script": "test-fixtures/script1.sh", - }) + } - scripts, err := p.collectScripts(conf) + scripts, err := collectScripts(schema.TestResourceDataRaw( + t, p.Schema, conf)) if err != nil { t.Fatalf("err: %v", err) } @@ -129,16 +129,17 @@ func TestResourceProvider_CollectScripts_script(t *testing.T) { } func TestResourceProvider_CollectScripts_scripts(t *testing.T) { - p := new(ResourceProvisioner) - conf := testConfig(t, map[string]interface{}{ + p := Provisioner().(*schema.Provisioner) + conf := map[string]interface{}{ "scripts": []interface{}{ "test-fixtures/script1.sh", "test-fixtures/script1.sh", "test-fixtures/script1.sh", }, - }) + } - scripts, err := p.collectScripts(conf) + scripts, err := collectScripts(schema.TestResourceDataRaw( + t, p.Schema, conf)) if err != nil { t.Fatalf("err: %v", err) } diff --git a/helper/schema/testing.go b/helper/schema/testing.go new file mode 100644 index 000000000..9765bdbc6 --- /dev/null +++ b/helper/schema/testing.go @@ -0,0 +1,30 @@ +package schema + +import ( + "testing" + + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/terraform" +) + +// TestResourceDataRaw creates a ResourceData from a raw configuration map. +func TestResourceDataRaw( + t *testing.T, schema map[string]*Schema, raw map[string]interface{}) *ResourceData { + c, err := config.NewRawConfig(raw) + if err != nil { + t.Fatalf("err: %s", err) + } + + sm := schemaMap(schema) + diff, err := sm.Diff(nil, terraform.NewResourceConfig(c)) + if err != nil { + t.Fatalf("err: %s", err) + } + + result, err := sm.Data(nil, diff) + if err != nil { + t.Fatalf("err: %s", err) + } + + return result +}