diff --git a/Makefile b/Makefile index 0324eca9c..e8d701770 100644 --- a/Makefile +++ b/Makefile @@ -6,12 +6,6 @@ VERSION?="0.3.44" # "make protobuf". generate: go generate ./... - # go fmt doesn't support -mod=vendor but it still wants to populate the - # module cache with everything in go.mod even though formatting requires - # no dependencies, and so we're disabling modules mode for this right - # now until the "go fmt" behavior is rationalized to either support the - # -mod= argument or _not_ try to install things. - GO111MODULE=off go fmt command/internal_plugin_list.go > /dev/null # We separate the protobuf generation because most development tasks on # Terraform do not involve changing protobuf files and protoc is not a diff --git a/builtin/bins/provisioner-file/main.go b/builtin/bins/provisioner-file/main.go deleted file mode 100644 index c0982b0b2..000000000 --- a/builtin/bins/provisioner-file/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/builtin/provisioners/file" - "github.com/hashicorp/terraform/plugin" -) - -func main() { - plugin.Serve(&plugin.ServeOpts{ - ProvisionerFunc: file.Provisioner, - }) -} diff --git a/builtin/bins/provisioner-local-exec/main.go b/builtin/bins/provisioner-local-exec/main.go deleted file mode 100644 index 2e0433ff5..000000000 --- a/builtin/bins/provisioner-local-exec/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/builtin/provisioners/local-exec" - "github.com/hashicorp/terraform/plugin" -) - -func main() { - plugin.Serve(&plugin.ServeOpts{ - ProvisionerFunc: localexec.Provisioner, - }) -} diff --git a/builtin/bins/provisioner-remote-exec/main.go b/builtin/bins/provisioner-remote-exec/main.go deleted file mode 100644 index 83ba43a98..000000000 --- a/builtin/bins/provisioner-remote-exec/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/builtin/provisioners/remote-exec" - "github.com/hashicorp/terraform/plugin" -) - -func main() { - plugin.Serve(&plugin.ServeOpts{ - ProvisionerFunc: remoteexec.Provisioner, - }) -} diff --git a/builtin/provisioners/file/resource_provisioner.go b/builtin/provisioners/file/resource_provisioner.go index 61941e978..559be022d 100644 --- a/builtin/provisioners/file/resource_provisioner.go +++ b/builtin/provisioners/file/resource_provisioner.go @@ -2,96 +2,131 @@ package file import ( "context" + "errors" "fmt" "io/ioutil" "os" + "sync" "github.com/hashicorp/terraform/communicator" - "github.com/hashicorp/terraform/internal/legacy/helper/schema" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/provisioners" "github.com/mitchellh/go-homedir" + "github.com/zclconf/go-cty/cty" ) -func Provisioner() terraform.ResourceProvisioner { - return &schema.Provisioner{ - Schema: map[string]*schema.Schema{ - "source": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"content"}, +func New() provisioners.Interface { + return &provisioner{} +} + +type provisioner struct { + // this stored from the running context, so that Stop() can + // cancel the transfer + mu sync.Mutex + cancel context.CancelFunc +} + +func (p *provisioner) GetSchema() (resp provisioners.GetSchemaResponse) { + schema := &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "source": { + Type: cty.String, + Optional: true, }, - "content": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"source"}, + "content": { + Type: cty.String, + Optional: true, }, - "destination": &schema.Schema{ - Type: schema.TypeString, + "destination": { + Type: cty.String, Required: true, }, }, - - ApplyFunc: applyFn, - ValidateFunc: validateFn, } + resp.Provisioner = schema + return resp } -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) +func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) { + cfg, err := p.GetSchema().Provisioner.CoerceValue(req.Config) if err != nil { - return err + resp.Diagnostics = resp.Diagnostics.Append(err) + } + + source := cfg.GetAttr("source") + content := cfg.GetAttr("content") + + switch { + case !source.IsNull() && !content.IsNull(): + resp.Diagnostics = resp.Diagnostics.Append(errors.New("Cannot set both 'source' and 'content'")) + return resp + case source.IsNull() && content.IsNull(): + resp.Diagnostics = resp.Diagnostics.Append(errors.New("Must provide one of 'source' or 'content'")) + return resp + } + + return resp +} + +func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { + p.mu.Lock() + ctx, cancel := context.WithCancel(context.Background()) + p.cancel = cancel + p.mu.Unlock() + + comm, err := communicator.New(req.Connection) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp } // Get the source - src, deleteSource, err := getSrc(data) + src, deleteSource, err := getSrc(req.Config) if err != nil { - return err + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp } if deleteSource { defer os.Remove(src) } // Begin the file copy - dst := data.Get("destination").(string) - + dst := req.Config.GetAttr("destination").AsString() 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'")) + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp } - return ws, es + return resp } // 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 { +func getSrc(v cty.Value) (string, bool, error) { + content := v.GetAttr("content") + src := v.GetAttr("source") + + switch { + case !content.IsNull(): file, err := ioutil.TempFile("", "tf-file-content") if err != nil { return "", true, err } - if _, err = file.WriteString(content.(string)); err != nil { + if _, err = file.WriteString(content.AsString()); err != nil { return "", true, err } return file.Name(), true, nil - } - expansion, err := homedir.Expand(src) - return expansion, false, err + case !src.IsNull(): + expansion, err := homedir.Expand(src.AsString()) + return expansion, false, err + + default: + panic("source and content cannot both be null") + } } // copyFiles is used to copy the files from a source to a destination @@ -138,5 +173,17 @@ func copyFiles(ctx context.Context, comm communicator.Communicator, src, dst str if err != nil { return fmt.Errorf("Upload failed: %v", err) } + return err } + +func (p *provisioner) Stop() error { + p.mu.Lock() + defer p.mu.Unlock() + p.cancel() + return nil +} + +func (p *provisioner) Close() error { + return nil +} diff --git a/builtin/provisioners/file/resource_provisioner_test.go b/builtin/provisioners/file/resource_provisioner_test.go index f56f7641c..9532fd517 100644 --- a/builtin/provisioners/file/resource_provisioner_test.go +++ b/builtin/provisioners/file/resource_provisioner_test.go @@ -3,110 +3,95 @@ package file import ( "testing" - "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/internal/legacy/helper/schema" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/provisioners" + "github.com/zclconf/go-cty/cty" ) -func TestResourceProvisioner_impl(t *testing.T) { - var _ terraform.ResourceProvisioner = Provisioner() -} - -func TestProvisioner(t *testing.T) { - if err := Provisioner().(*schema.Provisioner).InternalValidate(); err != nil { - t.Fatalf("err: %s", err) - } -} - func TestResourceProvider_Validate_good_source(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "source": "/tmp/foo", - "destination": "/tmp/bar", + v := cty.ObjectVal(map[string]cty.Value{ + "source": cty.StringVal("/tmp/foo"), + "destination": cty.StringVal("/tmp/bar"), }) - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) > 0 { - t.Fatalf("Errors: %v", errs) + resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{ + Config: v, + }) + + if len(resp.Diagnostics) > 0 { + t.Fatal(resp.Diagnostics.ErrWithWarnings()) } } func TestResourceProvider_Validate_good_content(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "content": "value to copy", - "destination": "/tmp/bar", + v := cty.ObjectVal(map[string]cty.Value{ + "content": cty.StringVal("value to copy"), + "destination": cty.StringVal("/tmp/bar"), }) - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) > 0 { - t.Fatalf("Errors: %v", errs) + resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{ + Config: v, + }) + + if len(resp.Diagnostics) > 0 { + t.Fatal(resp.Diagnostics.ErrWithWarnings()) } } func TestResourceProvider_Validate_good_unknown_variable_value(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "content": hcl2shim.UnknownVariableValue, - "destination": "/tmp/bar", + v := cty.ObjectVal(map[string]cty.Value{ + "content": cty.UnknownVal(cty.String), + "destination": cty.StringVal("/tmp/bar"), }) - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) > 0 { - t.Fatalf("Errors: %v", errs) + resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{ + Config: v, + }) + + if len(resp.Diagnostics) > 0 { + t.Fatal(resp.Diagnostics.ErrWithWarnings()) } } func TestResourceProvider_Validate_bad_not_destination(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "source": "nope", + v := cty.ObjectVal(map[string]cty.Value{ + "source": cty.StringVal("nope"), }) - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) == 0 { - t.Fatalf("Should have errors") + resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{ + Config: v, + }) + + if !resp.Diagnostics.HasErrors() { + t.Fatal("Should have errors") } } func TestResourceProvider_Validate_bad_no_source(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "destination": "/tmp/bar", + v := cty.ObjectVal(map[string]cty.Value{ + "destination": cty.StringVal("/tmp/bar"), }) - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) == 0 { - t.Fatalf("Should have errors") + resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{ + Config: v, + }) + + if !resp.Diagnostics.HasErrors() { + t.Fatal("Should have errors") } } func TestResourceProvider_Validate_bad_to_many_src(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "source": "nope", - "content": "value to copy", - "destination": "/tmp/bar", + v := cty.ObjectVal(map[string]cty.Value{ + "source": cty.StringVal("nope"), + "content": cty.StringVal("vlue to copy"), + "destination": cty.StringVal("/tmp/bar"), }) - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) == 0 { - t.Fatalf("Should have errors") - } -} + resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{ + Config: v, + }) -func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig { - return terraform.NewResourceConfigRaw(c) + if !resp.Diagnostics.HasErrors() { + t.Fatal("Should have errors") + } } diff --git a/builtin/provisioners/local-exec/resource_provisioner.go b/builtin/provisioners/local-exec/resource_provisioner.go index 12c69161f..6b03725dc 100644 --- a/builtin/provisioners/local-exec/resource_provisioner.go +++ b/builtin/provisioners/local-exec/resource_provisioner.go @@ -7,11 +7,13 @@ import ( "os" "os/exec" "runtime" + "sync" "github.com/armon/circbuf" - "github.com/hashicorp/terraform/internal/legacy/helper/schema" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/provisioners" "github.com/mitchellh/go-linereader" + "github.com/zclconf/go-cty/cty" ) const ( @@ -21,59 +23,79 @@ const ( maxBufSize = 8 * 1024 ) -func Provisioner() terraform.ResourceProvisioner { - return &schema.Provisioner{ - Schema: map[string]*schema.Schema{ - "command": &schema.Schema{ - Type: schema.TypeString, +func New() provisioners.Interface { + return &provisioner{} +} + +type provisioner struct { + // this stored from the running context, so that Stop() can cancel the + // command + mu sync.Mutex + cancel context.CancelFunc +} + +func (p *provisioner) GetSchema() (resp provisioners.GetSchemaResponse) { + schema := &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "command": { + Type: cty.String, Required: true, }, - "interpreter": &schema.Schema{ - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, + "interpreter": { + Type: cty.List(cty.String), Optional: true, }, - "working_dir": &schema.Schema{ - Type: schema.TypeString, + "working_dir": { + Type: cty.String, Optional: true, }, - "environment": &schema.Schema{ - Type: schema.TypeMap, + "environment": { + Type: cty.Map(cty.String), Optional: true, }, }, - - ApplyFunc: applyFn, } + + resp.Provisioner = schema + return resp } -func applyFn(ctx context.Context) error { - data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData) - o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput) +func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) { + if _, err := p.GetSchema().Provisioner.CoerceValue(req.Config); err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + } + return resp +} - command := data.Get("command").(string) +func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { + p.mu.Lock() + ctx, cancel := context.WithCancel(context.Background()) + p.cancel = cancel + p.mu.Unlock() + + command := req.Config.GetAttr("command").AsString() if command == "" { - return fmt.Errorf("local-exec provisioner command must be a non-empty string") + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("local-exec provisioner command must be a non-empty string")) + return resp } - // Execute the command with env - environment := data.Get("environment").(map[string]interface{}) - + envVal := req.Config.GetAttr("environment") var env []string - for k := range environment { - entry := fmt.Sprintf("%s=%s", k, environment[k].(string)) - env = append(env, entry) + + if !envVal.IsNull() { + for k, v := range envVal.AsValueMap() { + entry := fmt.Sprintf("%s=%s", k, v.AsString()) + env = append(env, entry) + } } // Execute the command using a shell - interpreter := data.Get("interpreter").([]interface{}) + intrVal := req.Config.GetAttr("interpreter") var cmdargs []string - if len(interpreter) > 0 { - for _, i := range interpreter { - if arg, ok := i.(string); ok { - cmdargs = append(cmdargs, arg) - } + if !intrVal.IsNull() && intrVal.LengthInt() > 0 { + for _, v := range intrVal.AsValueSlice() { + cmdargs = append(cmdargs, v.AsString()) } } else { if runtime.GOOS == "windows" { @@ -82,9 +104,13 @@ func applyFn(ctx context.Context) error { cmdargs = []string{"/bin/sh", "-c"} } } + cmdargs = append(cmdargs, command) - workingdir := data.Get("working_dir").(string) + workingdir := "" + if wdVal := req.Config.GetAttr("working_dir"); !wdVal.IsNull() { + workingdir = wdVal.AsString() + } // Setup the reader that will read the output from the command. // We use an os.Pipe so that the *os.File can be passed directly to the @@ -92,7 +118,8 @@ func applyFn(ctx context.Context) error { // See golang.org/issue/18874 pr, pw, err := os.Pipe() if err != nil { - return fmt.Errorf("failed to initialize pipe for output: %s", err) + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("failed to initialize pipe for output: %s", err)) + return resp } var cmdEnv []string @@ -118,10 +145,10 @@ func applyFn(ctx context.Context) error { // copy the teed output to the UI output copyDoneCh := make(chan struct{}) - go copyOutput(o, tee, copyDoneCh) + go copyUIOutput(req.UIOutput, tee, copyDoneCh) // Output what we're about to run - o.Output(fmt.Sprintf("Executing: %q", cmdargs)) + req.UIOutput.Output(fmt.Sprintf("Executing: %q", cmdargs)) // Start the command err = cmd.Start() @@ -142,14 +169,26 @@ func applyFn(ctx context.Context) error { } if err != nil { - return fmt.Errorf("Error running command '%s': %v. Output: %s", - command, err, output.Bytes()) + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Error running command '%s': %v. Output: %s", + command, err, output.Bytes())) + return resp } + return resp +} + +func (p *provisioner) Stop() error { + p.mu.Lock() + defer p.mu.Unlock() + p.cancel() return nil } -func copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) { +func (p *provisioner) Close() error { + return nil +} + +func copyUIOutput(o provisioners.UIOutput, r io.Reader, doneCh chan<- struct{}) { defer close(doneCh) lr := linereader.New(r) for line := range lr.Ch { diff --git a/builtin/provisioners/local-exec/resource_provisioner_test.go b/builtin/provisioners/local-exec/resource_provisioner_test.go index 32ccfa7e6..f828cc8fd 100644 --- a/builtin/provisioners/local-exec/resource_provisioner_test.go +++ b/builtin/provisioners/local-exec/resource_provisioner_test.go @@ -7,31 +7,30 @@ import ( "testing" "time" - "github.com/hashicorp/terraform/internal/legacy/helper/schema" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/provisioners" + "github.com/mitchellh/cli" + "github.com/zclconf/go-cty/cty" ) -func TestResourceProvisioner_impl(t *testing.T) { - var _ terraform.ResourceProvisioner = Provisioner() -} - -func TestProvisioner(t *testing.T) { - if err := Provisioner().(*schema.Provisioner).InternalValidate(); err != nil { - t.Fatalf("err: %s", err) - } -} - func TestResourceProvider_Apply(t *testing.T) { defer os.Remove("test_out") - c := testConfig(t, map[string]interface{}{ - "command": "echo foo > test_out", + output := cli.NewMockUi() + p := New() + schema := p.GetSchema().Provisioner + c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "command": cty.StringVal("echo foo > test_out"), + })) + if err != nil { + t.Fatal(err) + } + + resp := p.ProvisionResource(provisioners.ProvisionResourceRequest{ + Config: c, + UIOutput: output, }) - output := new(terraform.MockUIOutput) - p := Provisioner() - - if err := p.Apply(output, nil, c); err != nil { - t.Fatalf("err: %v", err) + if resp.Diagnostics.HasErrors() { + t.Fatalf("err: %v", resp.Diagnostics.Err()) } // Check the file @@ -48,14 +47,18 @@ func TestResourceProvider_Apply(t *testing.T) { } func TestResourceProvider_stop(t *testing.T) { - c := testConfig(t, map[string]interface{}{ + output := cli.NewMockUi() + p := New() + schema := p.GetSchema().Provisioner + + c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ // bash/zsh/ksh will exec a single command in the same process. This // makes certain there's a subprocess in the shell. - "command": "sleep 30; sleep 30", - }) - - output := new(terraform.MockUIOutput) - p := Provisioner() + "command": cty.StringVal("sleep 30; sleep 30"), + })) + if err != nil { + t.Fatal(err) + } doneCh := make(chan struct{}) startTime := time.Now() @@ -65,7 +68,10 @@ func TestResourceProvider_stop(t *testing.T) { // Because p.Apply is called in a goroutine, trying to t.Fatal() on its // result would be ignored or would cause a panic if the parent goroutine // has already completed. - _ = p.Apply(output, nil, c) + _ = p.ProvisionResource(provisioners.ProvisionResourceRequest{ + Config: c, + UIOutput: output, + }) }() mustExceed := (50 * time.Millisecond) @@ -90,51 +96,32 @@ func TestResourceProvider_stop(t *testing.T) { } } -func TestResourceProvider_Validate_good(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "command": "echo foo", - }) - - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) > 0 { - t.Fatalf("Errors: %v", errs) - } -} - -func TestResourceProvider_Validate_missing(t *testing.T) { - c := testConfig(t, map[string]interface{}{}) - - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) == 0 { - t.Fatalf("Should have errors") - } -} - -func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig { - return terraform.NewResourceConfigRaw(c) -} - func TestResourceProvider_ApplyCustomInterpreter(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "interpreter": []interface{}{"echo", "is"}, - "command": "not really an interpreter", - }) + output := cli.NewMockUi() + p := New() - output := new(terraform.MockUIOutput) - p := Provisioner() + schema := p.GetSchema().Provisioner - if err := p.Apply(output, nil, c); err != nil { - t.Fatalf("err: %v", err) + c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "interpreter": cty.ListVal([]cty.Value{cty.StringVal("echo"), cty.StringVal("is")}), + "command": cty.StringVal("not really an interpreter"), + })) + if err != nil { + t.Fatal(err) } - got := strings.TrimSpace(output.OutputMessage) - want := "is not really an interpreter" + resp := p.ProvisionResource(provisioners.ProvisionResourceRequest{ + Config: c, + UIOutput: output, + }) + + if resp.Diagnostics.HasErrors() { + t.Fatal(resp.Diagnostics.Err()) + } + + got := strings.TrimSpace(output.OutputWriter.String()) + want := `Executing: ["echo" "is" "not really an interpreter"] +is not really an interpreter` if got != want { t.Errorf("wrong output\ngot: %s\nwant: %s", got, want) } @@ -145,16 +132,25 @@ func TestResourceProvider_ApplyCustomWorkingDirectory(t *testing.T) { os.Mkdir(testdir, 0755) defer os.Remove(testdir) - c := testConfig(t, map[string]interface{}{ - "working_dir": testdir, - "command": "echo `pwd`", + output := cli.NewMockUi() + p := New() + schema := p.GetSchema().Provisioner + + c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "working_dir": cty.StringVal(testdir), + "command": cty.StringVal("echo `pwd`"), + })) + if err != nil { + t.Fatal(err) + } + + resp := p.ProvisionResource(provisioners.ProvisionResourceRequest{ + Config: c, + UIOutput: output, }) - output := new(terraform.MockUIOutput) - p := Provisioner() - - if err := p.Apply(output, nil, c); err != nil { - t.Fatalf("err: %v", err) + if resp.Diagnostics.HasErrors() { + t.Fatal(resp.Diagnostics.Err()) } dir, err := os.Getwd() @@ -162,32 +158,41 @@ func TestResourceProvider_ApplyCustomWorkingDirectory(t *testing.T) { t.Fatalf("err: %v", err) } - got := strings.TrimSpace(output.OutputMessage) - want := dir + "/" + testdir + got := strings.TrimSpace(output.OutputWriter.String()) + want := "Executing: [\"/bin/sh\" \"-c\" \"echo `pwd`\"]\n" + dir + "/" + testdir if got != want { t.Errorf("wrong output\ngot: %s\nwant: %s", got, want) } } func TestResourceProvider_ApplyCustomEnv(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "command": "echo $FOO $BAR $BAZ", - "environment": map[string]interface{}{ - "FOO": "BAR", - "BAR": 1, - "BAZ": "true", - }, - }) + output := cli.NewMockUi() + p := New() + schema := p.GetSchema().Provisioner - output := new(terraform.MockUIOutput) - p := Provisioner() - - if err := p.Apply(output, nil, c); err != nil { - t.Fatalf("err: %v", err) + c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "command": cty.StringVal("echo $FOO $BAR $BAZ"), + "environment": cty.MapVal(map[string]cty.Value{ + "FOO": cty.StringVal("BAR"), + "BAR": cty.StringVal("1"), + "BAZ": cty.StringVal("true"), + }), + })) + if err != nil { + t.Fatal(err) } - got := strings.TrimSpace(output.OutputMessage) - want := "BAR 1 true" + resp := p.ProvisionResource(provisioners.ProvisionResourceRequest{ + Config: c, + UIOutput: output, + }) + if resp.Diagnostics.HasErrors() { + t.Fatal(resp.Diagnostics.Err()) + } + + got := strings.TrimSpace(output.OutputWriter.String()) + want := `Executing: ["/bin/sh" "-c" "echo $FOO $BAR $BAZ"] +BAR 1 true` if got != want { t.Errorf("wrong output\ngot: %s\nwant: %s", got, want) } diff --git a/builtin/provisioners/remote-exec/resource_provisioner.go b/builtin/provisioners/remote-exec/resource_provisioner.go index 7e74d1ce9..adbb0d50d 100644 --- a/builtin/provisioners/remote-exec/resource_provisioner.go +++ b/builtin/provisioners/remote-exec/resource_provisioner.go @@ -3,92 +3,135 @@ package remoteexec import ( "bytes" "context" + "errors" "fmt" "io" "io/ioutil" "log" "os" "strings" - "time" + "sync" "github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/internal/legacy/helper/schema" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/provisioners" "github.com/mitchellh/go-linereader" + "github.com/zclconf/go-cty/cty" ) -// maxBackoffDealy is the maximum delay between retry attempts -var maxBackoffDelay = 10 * time.Second -var initialBackoffDelay = time.Second - -func Provisioner() terraform.ResourceProvisioner { - return &schema.Provisioner{ - Schema: map[string]*schema.Schema{ - "inline": { - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, - PromoteSingle: true, - Optional: true, - ConflictsWith: []string{"script", "scripts"}, - }, - - "script": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"inline", "scripts"}, - }, - - "scripts": { - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - ConflictsWith: []string{"script", "inline"}, - }, - }, - - ApplyFunc: applyFn, - } +func New() provisioners.Interface { + return &provisioner{} } -// Apply executes the remote exec provisioner -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) +type provisioner struct { + // this stored from the running context, so that Stop() can cancel the + // command + mu sync.Mutex + cancel context.CancelFunc +} - // Get a new communicator - comm, err := communicator.New(connState) +func (p *provisioner) GetSchema() (resp provisioners.GetSchemaResponse) { + schema := &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "inline": { + Type: cty.List(cty.String), + Optional: true, + }, + "script": { + Type: cty.String, + Optional: true, + }, + "scripts": { + Type: cty.List(cty.String), + Optional: true, + }, + }, + } + + resp.Provisioner = schema + return resp +} + +func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) { + cfg, err := p.GetSchema().Provisioner.CoerceValue(req.Config) if err != nil { - return err + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + + inline := cfg.GetAttr("inline") + script := cfg.GetAttr("script") + scripts := cfg.GetAttr("scripts") + + set := 0 + if !inline.IsNull() { + set++ + } + if !script.IsNull() { + set++ + } + if !scripts.IsNull() { + set++ + } + if set != 1 { + resp.Diagnostics = resp.Diagnostics.Append(errors.New( + `only one of "inline", "script", or "scripts" must be set`)) + } + return resp +} + +func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { + p.mu.Lock() + ctx, cancel := context.WithCancel(context.Background()) + p.cancel = cancel + p.mu.Unlock() + + comm, err := communicator.New(req.Connection) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp } // Collect the scripts - scripts, err := collectScripts(data) + scripts, err := collectScripts(req.Config) if err != nil { - return err + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp } for _, s := range scripts { defer s.Close() } // Copy and execute each script - if err := runScripts(ctx, o, comm, scripts); err != nil { - return err + if err := runScripts(ctx, req.UIOutput, comm, scripts); err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp } + return resp +} + +func (p *provisioner) Stop() error { + p.mu.Lock() + defer p.mu.Unlock() + p.cancel() + return nil +} + +func (p *provisioner) Close() error { return nil } // generateScripts takes the configuration and creates a script from each inline config -func generateScripts(d *schema.ResourceData) ([]string, error) { +func generateScripts(inline cty.Value) ([]string, error) { var lines []string - for _, l := range d.Get("inline").([]interface{}) { - line, ok := l.(string) - if !ok { - return nil, fmt.Errorf("Error parsing %v as a string", l) + for _, l := range inline.AsValueSlice() { + s := l.AsString() + if s == "" { + return nil, errors.New("invalid empty string in 'scripts'") } - lines = append(lines, line) + lines = append(lines, s) } lines = append(lines, "") @@ -97,10 +140,10 @@ func generateScripts(d *schema.ResourceData) ([]string, error) { // collectScripts is used to collect all the scripts we need // to execute in preparation for copying them. -func collectScripts(d *schema.ResourceData) ([]io.ReadCloser, error) { +func collectScripts(v cty.Value) ([]io.ReadCloser, error) { // Check if inline - if _, ok := d.GetOk("inline"); ok { - scripts, err := generateScripts(d) + if inline := v.GetAttr("inline"); !inline.IsNull() { + scripts, err := generateScripts(inline) if err != nil { return nil, err } @@ -115,21 +158,21 @@ func collectScripts(d *schema.ResourceData) ([]io.ReadCloser, error) { // Collect scripts var scripts []string - if script, ok := d.GetOk("script"); ok { - scr, ok := script.(string) - if !ok { - return nil, fmt.Errorf("Error parsing script %v as string", script) + if script := v.GetAttr("script"); !script.IsNull() { + s := script.AsString() + if s == "" { + return nil, errors.New("invalid empty string in 'script'") } - scripts = append(scripts, scr) + scripts = append(scripts, s) } - if scriptList, ok := d.GetOk("scripts"); ok { - for _, script := range scriptList.([]interface{}) { - scr, ok := script.(string) - if !ok { - return nil, fmt.Errorf("Error parsing script %v as string", script) + if scriptList := v.GetAttr("scripts"); !scriptList.IsNull() { + for _, script := range scriptList.AsValueSlice() { + s := script.AsString() + if s == "" { + return nil, errors.New("invalid empty string in 'script'") } - scripts = append(scripts, scr) + scripts = append(scripts, script.AsString()) } } @@ -151,12 +194,7 @@ func collectScripts(d *schema.ResourceData) ([]io.ReadCloser, error) { } // runScripts is used to copy and execute a set of scripts -func runScripts( - ctx context.Context, - o terraform.UIOutput, - comm communicator.Communicator, - scripts []io.ReadCloser) error { - +func runScripts(ctx context.Context, o provisioners.UIOutput, comm communicator.Communicator, scripts []io.ReadCloser) error { retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout()) defer cancel() @@ -182,8 +220,8 @@ func runScripts( defer outW.Close() defer errW.Close() - go copyOutput(o, outR) - go copyOutput(o, errR) + go copyUIOutput(o, outR) + go copyUIOutput(o, errR) remotePath := comm.ScriptPath() @@ -216,8 +254,7 @@ func runScripts( return nil } -func copyOutput( - o terraform.UIOutput, r io.Reader) { +func copyUIOutput(o provisioners.UIOutput, r io.Reader) { lr := linereader.New(r) for line := range lr.Ch { o.Output(line) diff --git a/builtin/provisioners/remote-exec/resource_provisioner_test.go b/builtin/provisioners/remote-exec/resource_provisioner_test.go index 014bb6410..ac5aa64ef 100644 --- a/builtin/provisioners/remote-exec/resource_provisioner_test.go +++ b/builtin/provisioners/remote-exec/resource_provisioner_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "io" + "log" "testing" "time" @@ -11,44 +12,34 @@ import ( "github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/provisioners" + "github.com/mitchellh/cli" + "github.com/zclconf/go-cty/cty" ) -func TestResourceProvisioner_impl(t *testing.T) { - var _ terraform.ResourceProvisioner = Provisioner() -} - -func TestProvisioner(t *testing.T) { - if err := Provisioner().(*schema.Provisioner).InternalValidate(); err != nil { - t.Fatalf("err: %s", err) - } -} - func TestResourceProvider_Validate_good(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "inline": "echo foo", + c := cty.ObjectVal(map[string]cty.Value{ + "inline": cty.ListVal([]cty.Value{cty.StringVal("echo foo")}), }) - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) > 0 { - t.Fatalf("Errors: %v", errs) + resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{ + Config: c, + }) + if len(resp.Diagnostics) > 0 { + t.Fatal(resp.Diagnostics.ErrWithWarnings()) } } func TestResourceProvider_Validate_bad(t *testing.T) { - c := testConfig(t, map[string]interface{}{ - "invalid": "nope", + c := cty.ObjectVal(map[string]cty.Value{ + "invalid": cty.StringVal("nope"), }) - warn, errs := Provisioner().Validate(c) - if len(warn) > 0 { - t.Fatalf("Warnings: %v", warn) - } - if len(errs) == 0 { + resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{ + Config: c, + }) + if !resp.Diagnostics.HasErrors() { t.Fatalf("Should have errors") } } @@ -59,17 +50,13 @@ exit 0 ` func TestResourceProvider_generateScript(t *testing.T) { - conf := map[string]interface{}{ - "inline": []interface{}{ - "cd /tmp", - "wget http://foobar", - "exit 0", - }, - } + inline := cty.ListVal([]cty.Value{ + cty.StringVal("cd /tmp"), + cty.StringVal("wget http://foobar"), + cty.StringVal("exit 0"), + }) - out, err := generateScripts( - schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, conf), - ) + out, err := generateScripts(inline) if err != nil { t.Fatalf("err: %v", err) } @@ -84,34 +71,28 @@ func TestResourceProvider_generateScript(t *testing.T) { } func TestResourceProvider_generateScriptEmptyInline(t *testing.T) { - p := Provisioner().(*schema.Provisioner) - conf := map[string]interface{}{ - "inline": []interface{}{""}, - } + inline := cty.ListVal([]cty.Value{cty.StringVal("")}) - _, err := generateScripts(schema.TestResourceDataRaw( - t, p.Schema, conf)) + _, err := generateScripts(inline) if err == nil { t.Fatal("expected error, got none") } - if !strings.Contains(err.Error(), "Error parsing") { - t.Fatalf("expected parsing error, got: %s", err) + if !strings.Contains(err.Error(), "empty string") { + t.Fatalf("expected empty string error, got: %s", err) } } func TestResourceProvider_CollectScripts_inline(t *testing.T) { - conf := map[string]interface{}{ - "inline": []interface{}{ - "cd /tmp", - "wget http://foobar", - "exit 0", - }, + conf := map[string]cty.Value{ + "inline": cty.ListVal([]cty.Value{ + cty.StringVal("cd /tmp"), + cty.StringVal("wget http://foobar"), + cty.StringVal("exit 0"), + }), } - scripts, err := collectScripts( - schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, conf), - ) + scripts, err := collectScripts(cty.ObjectVal(conf)) if err != nil { t.Fatalf("err: %v", err) } @@ -132,13 +113,19 @@ func TestResourceProvider_CollectScripts_inline(t *testing.T) { } func TestResourceProvider_CollectScripts_script(t *testing.T) { - conf := map[string]interface{}{ - "script": "testdata/script1.sh", + p := New() + schema := p.GetSchema().Provisioner + + conf, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "scripts": cty.ListVal([]cty.Value{ + cty.StringVal("testdata/script1.sh"), + }), + })) + if err != nil { + t.Fatal(err) } - scripts, err := collectScripts( - schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, conf), - ) + scripts, err := collectScripts(conf) if err != nil { t.Fatalf("err: %v", err) } @@ -159,17 +146,21 @@ func TestResourceProvider_CollectScripts_script(t *testing.T) { } func TestResourceProvider_CollectScripts_scripts(t *testing.T) { - conf := map[string]interface{}{ - "scripts": []interface{}{ - "testdata/script1.sh", - "testdata/script1.sh", - "testdata/script1.sh", - }, + p := New() + schema := p.GetSchema().Provisioner + + conf, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "scripts": cty.ListVal([]cty.Value{ + cty.StringVal("testdata/script1.sh"), + cty.StringVal("testdata/script1.sh"), + cty.StringVal("testdata/script1.sh"), + }), + })) + if err != nil { + log.Fatal(err) } - scripts, err := collectScripts( - schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, conf), - ) + scripts, err := collectScripts(conf) if err != nil { t.Fatalf("err: %v", err) } @@ -192,25 +183,28 @@ func TestResourceProvider_CollectScripts_scripts(t *testing.T) { } func TestResourceProvider_CollectScripts_scriptsEmpty(t *testing.T) { - p := Provisioner().(*schema.Provisioner) - conf := map[string]interface{}{ - "scripts": []interface{}{""}, + p := New() + schema := p.GetSchema().Provisioner + + conf, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "scripts": cty.ListVal([]cty.Value{cty.StringVal("")}), + })) + if err != nil { + t.Fatal(err) } - _, err := collectScripts(schema.TestResourceDataRaw( - t, p.Schema, conf)) - + _, err = collectScripts(conf) if err == nil { t.Fatal("expected error") } - if !strings.Contains(err.Error(), "Error parsing") { - t.Fatalf("Expected parsing error, got: %s", err) + if !strings.Contains(err.Error(), "empty string") { + t.Fatalf("Expected empty string error, got: %s", err) } } func TestProvisionerTimeout(t *testing.T) { - o := new(terraform.MockUIOutput) + o := cli.NewMockUi() c := new(communicator.MockCommunicator) disconnected := make(chan struct{}) @@ -231,13 +225,11 @@ func TestProvisionerTimeout(t *testing.T) { c.UploadScripts = map[string]string{"hello": "echo hello"} c.RemoteScriptPath = "hello" - p := Provisioner().(*schema.Provisioner) - conf := map[string]interface{}{ - "inline": []interface{}{"echo hello"}, + conf := map[string]cty.Value{ + "inline": cty.ListVal([]cty.Value{cty.StringVal("echo hello")}), } - scripts, err := collectScripts(schema.TestResourceDataRaw( - t, p.Schema, conf)) + scripts, err := collectScripts(cty.ObjectVal(conf)) if err != nil { t.Fatal(err) } diff --git a/command/e2etest/provider_dev_test.go b/command/e2etest/provider_dev_test.go index ba8f9c61c..9d7a0eaeb 100644 --- a/command/e2etest/provider_dev_test.go +++ b/command/e2etest/provider_dev_test.go @@ -33,7 +33,7 @@ func TestProviderDevOverrides(t *testing.T) { // such as if it stops being buildable into an independent executable. providerExeDir := filepath.Join(tf.WorkDir(), "pkgdir") providerExePrefix := filepath.Join(providerExeDir, "terraform-provider-test_") - providerExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/legacy/builtin/bins/provider-test", providerExePrefix) + providerExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple/main", providerExePrefix) t.Logf("temporary provider executable is %s", providerExe) err := ioutil.WriteFile(filepath.Join(tf.WorkDir(), "dev.tfrc"), []byte(fmt.Sprintf(` diff --git a/command/e2etest/provisioner_plugin_test.go b/command/e2etest/provisioner_plugin_test.go new file mode 100644 index 000000000..d801eaef7 --- /dev/null +++ b/command/e2etest/provisioner_plugin_test.go @@ -0,0 +1,65 @@ +package e2etest + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/hashicorp/terraform/e2e" +) + +// TestProviderDevOverrides is a test that terraform can execute a 3rd party +// provisioner plugin. +func TestProvisionerPlugin(t *testing.T) { + t.Parallel() + + // This test reaches out to releases.hashicorp.com to download the + // template and null providers, so it can only run if network access is + // allowed. + skipIfCannotAccessNetwork(t) + + tf := e2e.NewBinary(terraformBin, "testdata/provisioner-plugin") + defer tf.Close() + + // In order to do a decent end-to-end test for this case we will need a + // real enough provisioner plugin to try to run and make sure we are able + // to actually run it. Here will build the local-exec provisioner into a + // binary called test-provisioner + provisionerExePrefix := filepath.Join(tf.WorkDir(), "terraform-provisioner-test_") + provisionerExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provisioner-local-exec/main", provisionerExePrefix) + + // provisioners must use the old binary name format, so rename this binary + newExe := filepath.Join(tf.WorkDir(), "terraform-provisioner-test") + if _, err := os.Stat(newExe); !os.IsNotExist(err) { + t.Fatalf("%q already exists", newExe) + } + if err := os.Rename(provisionerExe, newExe); err != nil { + t.Fatalf("error renaming provisioner binary: %v", err) + } + provisionerExe = newExe + + t.Logf("temporary provisioner executable is %s", provisionerExe) + + //// INIT + _, stderr, err := tf.Run("init") + if err != nil { + t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) + } + + //// PLAN + _, stderr, err = tf.Run("plan", "-out=tfplan") + if err != nil { + t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) + } + + //// APPLY + stdout, stderr, err := tf.Run("apply", "tfplan") + if err != nil { + t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) + } + + if !strings.Contains(stdout, "HelloProvisioner") { + t.Fatalf("missing provisioner output:\n%s", stdout) + } +} diff --git a/command/e2etest/testdata/provider-dev-override/provider-dev-override.tf b/command/e2etest/testdata/provider-dev-override/provider-dev-override.tf index 195cb1a3b..9c629f722 100644 --- a/command/e2etest/testdata/provider-dev-override/provider-dev-override.tf +++ b/command/e2etest/testdata/provider-dev-override/provider-dev-override.tf @@ -1,14 +1,11 @@ terraform { required_providers { - test = { + simple = { source = "example.com/test/test" version = "2.0.0" } } } -provider "test" { -} - -data "test_data_source" "test" { +data "simple_resource" "test" { } diff --git a/command/e2etest/testdata/provisioner-plugin/main.tf b/command/e2etest/testdata/provisioner-plugin/main.tf new file mode 100644 index 000000000..8e6268b96 --- /dev/null +++ b/command/e2etest/testdata/provisioner-plugin/main.tf @@ -0,0 +1,5 @@ +resource "null_resource" "a" { + provisioner "test" { + command = "echo HelloProvisioner" + } +} diff --git a/command/e2etest/testdata/test-provider/main.tf b/command/e2etest/testdata/test-provider/main.tf index 864643ef6..a4de134c8 100644 --- a/command/e2etest/testdata/test-provider/main.tf +++ b/command/e2etest/testdata/test-provider/main.tf @@ -1,6 +1,10 @@ -provider "test" { - +terraform { + required_providers { + simple = { + source = "hashicorp/test" + } + } } -resource "test_resource_signal" "test" { +resource "simple_resource" "test" { } diff --git a/command/e2etest/unmanaged_test.go b/command/e2etest/unmanaged_test.go index 0dd262b3b..c99760055 100644 --- a/command/e2etest/unmanaged_test.go +++ b/command/e2etest/unmanaged_test.go @@ -12,8 +12,8 @@ import ( "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" "github.com/hashicorp/terraform/e2e" - "github.com/hashicorp/terraform/internal/legacy/builtin/providers/test" - grpcplugin "github.com/hashicorp/terraform/internal/legacy/helper/plugin" + "github.com/hashicorp/terraform/internal/grpcwrap" + simple "github.com/hashicorp/terraform/internal/provider-simple" proto "github.com/hashicorp/terraform/internal/tfplugin5" tfplugin "github.com/hashicorp/terraform/plugin" ) @@ -42,7 +42,7 @@ type reattachConfigAddr struct { type providerServer struct { sync.Mutex - *grpcplugin.GRPCProviderServer + proto.ProviderServer planResourceChangeCalled bool applyResourceChangeCalled bool } @@ -52,7 +52,7 @@ func (p *providerServer) PlanResourceChange(ctx context.Context, req *proto.Plan defer p.Unlock() p.planResourceChangeCalled = true - return p.GRPCProviderServer.PlanResourceChange(ctx, req) + return p.ProviderServer.PlanResourceChange(ctx, req) } func (p *providerServer) ApplyResourceChange(ctx context.Context, req *proto.ApplyResourceChange_Request) (*proto.ApplyResourceChange_Response, error) { @@ -60,7 +60,7 @@ func (p *providerServer) ApplyResourceChange(ctx context.Context, req *proto.App defer p.Unlock() p.applyResourceChangeCalled = true - return p.GRPCProviderServer.ApplyResourceChange(ctx, req) + return p.ProviderServer.ApplyResourceChange(ctx, req) } func (p *providerServer) PlanResourceChangeCalled() bool { @@ -99,7 +99,7 @@ func TestUnmanagedSeparatePlan(t *testing.T) { reattachCh := make(chan *plugin.ReattachConfig) closeCh := make(chan struct{}) provider := &providerServer{ - GRPCProviderServer: grpcplugin.NewGRPCProviderServerShim(test.Provider()), + ProviderServer: grpcwrap.Provider(simple.Provider()), } ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -140,6 +140,7 @@ func TestUnmanagedSeparatePlan(t *testing.T) { }, }, }) + tf.AddEnv("TF_REATTACH_PROVIDERS=" + string(reattachStr)) tf.AddEnv("PLUGIN_PROTOCOL_VERSION=5") @@ -164,7 +165,7 @@ func TestUnmanagedSeparatePlan(t *testing.T) { } if !provider.PlanResourceChangeCalled() { - t.Error("PlanResourceChange not called on in-process provider") + t.Error("PlanResourceChange not called on un-managed provider") } //// APPLY @@ -174,7 +175,7 @@ func TestUnmanagedSeparatePlan(t *testing.T) { } if !provider.ApplyResourceChangeCalled() { - t.Error("ApplyResourceChange not called on in-process provider") + t.Error("ApplyResourceChange not called on un-managed provider") } provider.ResetApplyResourceChangeCalled() diff --git a/command/e2etest/version_test.go b/command/e2etest/version_test.go index 261877290..1f19ecf64 100644 --- a/command/e2etest/version_test.go +++ b/command/e2etest/version_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/hashicorp/terraform/e2e" - tfcore "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/version" ) func TestVersion(t *testing.T) { @@ -31,7 +31,7 @@ func TestVersion(t *testing.T) { t.Errorf("unexpected stderr output:\n%s", stderr) } - wantVersion := fmt.Sprintf("Terraform v%s", tfcore.VersionString()) + wantVersion := fmt.Sprintf("Terraform v%s", version.String()) if !strings.Contains(stdout, wantVersion) { t.Errorf("output does not contain our current version %q:\n%s", wantVersion, stdout) } @@ -63,7 +63,7 @@ func TestVersionWithProvider(t *testing.T) { t.Errorf("unexpected stderr output:\n%s", stderr) } - wantVersion := fmt.Sprintf("Terraform v%s", tfcore.VersionString()) + wantVersion := fmt.Sprintf("Terraform v%s", version.String()) if !strings.Contains(stdout, wantVersion) { t.Errorf("output does not contain our current version %q:\n%s", wantVersion, stdout) } diff --git a/command/internal_plugin.go b/command/internal_plugin.go deleted file mode 100644 index 33de8569a..000000000 --- a/command/internal_plugin.go +++ /dev/null @@ -1,97 +0,0 @@ -package command - -import ( - "fmt" - "log" - "strings" - - "github.com/hashicorp/terraform/plugin" - "github.com/kardianos/osext" -) - -// InternalPluginCommand is a Command implementation that allows plugins to be -// compiled into the main Terraform binary and executed via a subcommand. -type InternalPluginCommand struct { - Meta -} - -const TFSPACE = "-TFSPACE-" - -// BuildPluginCommandString builds a special string for executing internal -// plugins. It has the following format: -// -// /path/to/terraform-TFSPACE-internal-plugin-TFSPACE-terraform-provider-aws -// -// We split the string on -TFSPACE- to build the command executor. The reason we -// use -TFSPACE- is so we can support spaces in the /path/to/terraform part. -func BuildPluginCommandString(pluginType, pluginName string) (string, error) { - terraformPath, err := osext.Executable() - if err != nil { - return "", err - } - parts := []string{terraformPath, "internal-plugin", pluginType, pluginName} - return strings.Join(parts, TFSPACE), nil -} - -// Internal plugins do not support any CLI args, but we do receive flags that -// main.go:mergeEnvArgs has merged in from EnvCLI. Instead of making main.go -// aware of this exception, we strip all flags from our args. Flags are easily -// identified by the '-' prefix, ensured by the cli package used. -func StripArgFlags(args []string) []string { - argsNoFlags := []string{} - for i := range args { - if !strings.HasPrefix(args[i], "-") { - argsNoFlags = append(argsNoFlags, args[i]) - } - } - return argsNoFlags -} - -func (c *InternalPluginCommand) Run(args []string) int { - // strip flags from args, only use subcommands. - args = StripArgFlags(args) - - if len(args) != 2 { - log.Printf("Wrong number of args; expected: terraform internal-plugin pluginType pluginName") - return 1 - } - - pluginType := args[0] - pluginName := args[1] - - log.SetPrefix(fmt.Sprintf("%s-%s (internal) ", pluginName, pluginType)) - - switch pluginType { - case "provisioner": - pluginFunc, found := InternalProvisioners[pluginName] - if !found { - log.Printf("[ERROR] Could not load provisioner: %s", pluginName) - return 1 - } - log.Printf("[INFO] Starting provisioner plugin %s", pluginName) - plugin.Serve(&plugin.ServeOpts{ - ProvisionerFunc: pluginFunc, - }) - default: - log.Printf("[ERROR] Invalid plugin type %s", pluginType) - return 1 - } - - return 0 -} - -func (c *InternalPluginCommand) Help() string { - helpText := ` -Usage: terraform internal-plugin pluginType pluginName - - Runs an internally-compiled version of a plugin from the terraform binary. - - NOTE: this is an internal command and you should not call it yourself. -` - - return strings.TrimSpace(helpText) -} - -func (c *InternalPluginCommand) Synopsis() string { - return "internal plugin command" -} diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go deleted file mode 100644 index 06230bdfb..000000000 --- a/command/internal_plugin_list.go +++ /dev/null @@ -1,20 +0,0 @@ -// -// This file is automatically generated by scripts/generate-plugins.go -- Do not edit! -// -package command - -import ( - fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file" - localexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/local-exec" - remoteexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/remote-exec" - - "github.com/hashicorp/terraform/plugin" -) - -var InternalProviders = map[string]plugin.ProviderFunc{} - -var InternalProvisioners = map[string]plugin.ProvisionerFunc{ - "file": fileprovisioner.Provisioner, - "local-exec": localexecprovisioner.Provisioner, - "remote-exec": remoteexecprovisioner.Provisioner, -} diff --git a/command/internal_plugin_test.go b/command/internal_plugin_test.go deleted file mode 100644 index 4254090e5..000000000 --- a/command/internal_plugin_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package command - -import ( - "testing" -) - -func TestInternalPlugin_InternalProviders(t *testing.T) { - m := new(Meta) - providers := m.internalProviders() - // terraform is the only provider moved back to internal - for _, name := range []string{"terraform"} { - pf, ok := providers[name] - if !ok { - t.Errorf("Expected to find %s in InternalProviders", name) - } - - provider, err := pf() - if err != nil { - t.Fatal(err) - } - - if provider == nil { - t.Fatal("provider factory returned a nil provider") - } - } -} - -func TestInternalPlugin_InternalProvisioners(t *testing.T) { - for _, name := range []string{"file", "local-exec", "remote-exec"} { - if _, ok := InternalProvisioners[name]; !ok { - t.Errorf("Expected to find %s in InternalProvisioners", name) - } - } -} - -func TestInternalPlugin_BuildPluginCommandString(t *testing.T) { - actual, err := BuildPluginCommandString("provisioner", "remote-exec") - if err != nil { - t.Fatalf(err.Error()) - } - - expected := "-TFSPACE-internal-plugin-TFSPACE-provisioner-TFSPACE-remote-exec" - if actual[len(actual)-len(expected):] != expected { - t.Errorf("Expected command to end with %s; got:\n%s\n", expected, actual) - } -} - -func TestInternalPlugin_StripArgFlags(t *testing.T) { - actual := StripArgFlags([]string{"provisioner", "remote-exec", "-var-file=my_vars.tfvars", "-flag"}) - expected := []string{"provisioner", "remote-exec"} - // Must be same length and order. - if len(actual) != len(expected) || expected[0] != actual[0] || actual[1] != actual[1] { - t.Fatalf("Expected args to be exactly '%s', got '%s'", expected, actual) - } -} diff --git a/command/plugins.go b/command/plugins.go index 7de75841a..f57de348a 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -9,11 +9,13 @@ import ( "os/exec" "path/filepath" "runtime" - "strings" plugin "github.com/hashicorp/go-plugin" "github.com/kardianos/osext" + fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file" + localexec "github.com/hashicorp/terraform/builtin/provisioners/local-exec" + remoteexec "github.com/hashicorp/terraform/builtin/provisioners/remote-exec" "github.com/hashicorp/terraform/internal/logging" tfplugin "github.com/hashicorp/terraform/plugin" "github.com/hashicorp/terraform/plugin/discovery" @@ -134,8 +136,8 @@ func (m *Meta) provisionerFactories() map[string]provisioners.Factory { // Wire up the internal provisioners first. These might be overridden // by discovered provisioners below. - for name := range InternalProvisioners { - factories[name] = internalProvisionerFactory(discovery.PluginMeta{Name: name}) + for name, factory := range internalProvisionerFactories() { + factories[name] = factory } byName := plugins.ByName() @@ -151,29 +153,6 @@ func (m *Meta) provisionerFactories() map[string]provisioners.Factory { return factories } -func internalPluginClient(kind, name string) (*plugin.Client, error) { - cmdLine, err := BuildPluginCommandString(kind, name) - if err != nil { - return nil, err - } - - // See the docstring for BuildPluginCommandString for why we need to do - // this split here. - cmdArgv := strings.Split(cmdLine, TFSPACE) - - cfg := &plugin.ClientConfig{ - Cmd: exec.Command(cmdArgv[0], cmdArgv[1:]...), - HandshakeConfig: tfplugin.Handshake, - Managed: true, - VersionedPlugins: tfplugin.VersionedPlugins, - AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, - AutoMTLS: enableProviderAutoMTLS, - Logger: logging.NewLogger(kind), - } - - return plugin.NewClient(cfg), nil -} - func provisionerFactory(meta discovery.PluginMeta) provisioners.Factory { return func() (provisioners.Interface, error) { cfg := &plugin.ClientConfig{ @@ -190,13 +169,11 @@ func provisionerFactory(meta discovery.PluginMeta) provisioners.Factory { } } -func internalProvisionerFactory(meta discovery.PluginMeta) provisioners.Factory { - return func() (provisioners.Interface, error) { - client, err := internalPluginClient("provisioner", meta.Name) - if err != nil { - return nil, fmt.Errorf("[WARN] failed to build command line for internal plugin %q: %s", meta.Name, err) - } - return newProvisionerClient(client) +func internalProvisionerFactories() map[string]provisioners.Factory { + return map[string]provisioners.Factory{ + "file": provisioners.FactoryFixed(fileprovisioner.New()), + "local-exec": provisioners.FactoryFixed(localexec.New()), + "remote-exec": provisioners.FactoryFixed(remoteexec.New()), } } diff --git a/commands.go b/commands.go index 1db6dfb63..c889d37f1 100644 --- a/commands.go +++ b/commands.go @@ -194,12 +194,6 @@ func initCommands( }, nil }, - "internal-plugin": func() (cli.Command, error) { - return &command.InternalPluginCommand{ - Meta: meta, - }, nil - }, - "login": func() (cli.Command, error) { return &command.LoginCommand{ Meta: meta, diff --git a/communicator/communicator.go b/communicator/communicator.go index f3ca660f5..fd262c29a 100644 --- a/communicator/communicator.go +++ b/communicator/communicator.go @@ -9,16 +9,18 @@ import ( "time" "github.com/hashicorp/terraform/communicator/remote" + "github.com/hashicorp/terraform/communicator/shared" "github.com/hashicorp/terraform/communicator/ssh" "github.com/hashicorp/terraform/communicator/winrm" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/provisioners" + "github.com/zclconf/go-cty/cty" ) // Communicator is an interface that must be implemented by all communicators // used for any of the provisioners type Communicator interface { // Connect is used to setup the connection - Connect(terraform.UIOutput) error + Connect(provisioners.UIOutput) error // Disconnect is used to terminate the connection Disconnect() error @@ -43,13 +45,23 @@ type Communicator interface { } // New returns a configured Communicator or an error if the connection type is not supported -func New(s *terraform.InstanceState) (Communicator, error) { - connType := s.Ephemeral.ConnInfo["type"] +func New(v cty.Value) (Communicator, error) { + v, err := shared.ConnectionBlockSupersetSchema.CoerceValue(v) + if err != nil { + return nil, err + } + + typeVal := v.GetAttr("type") + connType := "" + if !typeVal.IsNull() { + connType = typeVal.AsString() + } + switch connType { case "ssh", "": // The default connection type is ssh, so if connType is empty use ssh - return ssh.New(s) + return ssh.New(v) case "winrm": - return winrm.New(s) + return winrm.New(v) default: return nil, fmt.Errorf("connection type '%s' not supported", connType) } diff --git a/communicator/communicator_mock.go b/communicator/communicator_mock.go index 9207b9438..b619560c0 100644 --- a/communicator/communicator_mock.go +++ b/communicator/communicator_mock.go @@ -8,7 +8,7 @@ import ( "time" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/provisioners" ) // MockCommunicator is an implementation of Communicator that can be used for tests. @@ -24,7 +24,7 @@ type MockCommunicator struct { } // Connect implementation of communicator.Communicator interface -func (c *MockCommunicator) Connect(o terraform.UIOutput) error { +func (c *MockCommunicator) Connect(o provisioners.UIOutput) error { return nil } diff --git a/communicator/communicator_test.go b/communicator/communicator_test.go index ba2383b8b..20cdd8ff3 100644 --- a/communicator/communicator_test.go +++ b/communicator/communicator_test.go @@ -8,29 +8,26 @@ import ( "testing" "time" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/zclconf/go-cty/cty" ) func TestCommunicator_new(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "telnet", - "host": "127.0.0.1", - }, - }, + cfg := map[string]cty.Value{ + "type": cty.StringVal("telnet"), + "host": cty.StringVal("127.0.0.1"), } - if _, err := New(r); err == nil { + + if _, err := New(cty.ObjectVal(cfg)); err == nil { t.Fatalf("expected error with telnet") } - r.Ephemeral.ConnInfo["type"] = "ssh" - if _, err := New(r); err != nil { + cfg["type"] = cty.StringVal("ssh") + if _, err := New(cty.ObjectVal(cfg)); err != nil { t.Fatalf("err: %v", err) } - r.Ephemeral.ConnInfo["type"] = "winrm" - if _, err := New(r); err != nil { + cfg["type"] = cty.StringVal("winrm") + if _, err := New(cty.ObjectVal(cfg)); err != nil { t.Fatalf("err: %v", err) } } diff --git a/communicator/shared/shared.go b/communicator/shared/shared.go index 39cb16961..509aadd28 100644 --- a/communicator/shared/shared.go +++ b/communicator/shared/shared.go @@ -3,8 +3,124 @@ package shared import ( "fmt" "net" + + "github.com/hashicorp/terraform/configs/configschema" + "github.com/zclconf/go-cty/cty" ) +// ConnectionBlockSupersetSchema is a schema representing the superset of all +// possible arguments for "connection" blocks across all supported connection +// types. +// +// This currently lives here because we've not yet updated our communicator +// subsystem to be aware of schema itself. Once that is done, we can remove +// this and use a type-specific schema from the communicator to validate +// exactly what is expected for a given connection type. +var ConnectionBlockSupersetSchema = &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + // Common attributes for both connection types + "host": { + Type: cty.String, + Required: true, + }, + "type": { + Type: cty.String, + Optional: true, + }, + "user": { + Type: cty.String, + Optional: true, + }, + "password": { + Type: cty.String, + Optional: true, + }, + "port": { + Type: cty.String, + Optional: true, + }, + "timeout": { + Type: cty.String, + Optional: true, + }, + "script_path": { + Type: cty.String, + Optional: true, + }, + // For type=ssh only (enforced in ssh communicator) + "target_platform": { + Type: cty.String, + Optional: true, + }, + "private_key": { + Type: cty.String, + Optional: true, + }, + "certificate": { + Type: cty.String, + Optional: true, + }, + "host_key": { + Type: cty.String, + Optional: true, + }, + "agent": { + Type: cty.Bool, + Optional: true, + }, + "agent_identity": { + Type: cty.String, + Optional: true, + }, + "bastion_host": { + Type: cty.String, + Optional: true, + }, + "bastion_host_key": { + Type: cty.String, + Optional: true, + }, + "bastion_port": { + Type: cty.Number, + Optional: true, + }, + "bastion_user": { + Type: cty.String, + Optional: true, + }, + "bastion_password": { + Type: cty.String, + Optional: true, + }, + "bastion_private_key": { + Type: cty.String, + Optional: true, + }, + "bastion_certificate": { + Type: cty.String, + Optional: true, + }, + + // For type=winrm only (enforced in winrm communicator) + "https": { + Type: cty.Bool, + Optional: true, + }, + "insecure": { + Type: cty.Bool, + Optional: true, + }, + "cacert": { + Type: cty.String, + Optional: true, + }, + "use_ntlm": { + Type: cty.Bool, + Optional: true, + }, + }, +} + // IpFormat formats the IP correctly, so we don't provide IPv6 address in an IPv4 format during node communication. We return the ip parameter as is if it's an IPv4 address or a hostname. func IpFormat(ip string) string { ipObj := net.ParseIP(ip) diff --git a/communicator/ssh/communicator.go b/communicator/ssh/communicator.go index 3e9c931e2..6963d8eb6 100644 --- a/communicator/ssh/communicator.go +++ b/communicator/ssh/communicator.go @@ -20,9 +20,12 @@ import ( "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/provisioners" + "github.com/zclconf/go-cty/cty" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" + + _ "github.com/hashicorp/terraform/internal/logging" ) const ( @@ -84,8 +87,8 @@ func (e fatalError) FatalError() error { } // New creates a new communicator implementation over SSH. -func New(s *terraform.InstanceState) (*Communicator, error) { - connInfo, err := parseConnectionInfo(s) +func New(v cty.Value) (*Communicator, error) { + connInfo, err := parseConnectionInfo(v) if err != nil { return nil, err } @@ -117,7 +120,7 @@ func New(s *terraform.InstanceState) (*Communicator, error) { } // Connect implementation of communicator.Communicator interface -func (c *Communicator) Connect(o terraform.UIOutput) (err error) { +func (c *Communicator) Connect(o provisioners.UIOutput) (err error) { // Grab a lock so we can modify our internal attributes c.lock.Lock() defer c.lock.Unlock() diff --git a/communicator/ssh/communicator_test.go b/communicator/ssh/communicator_test.go index 99bee1439..f2962c928 100644 --- a/communicator/ssh/communicator_test.go +++ b/communicator/ssh/communicator_test.go @@ -20,7 +20,7 @@ import ( "time" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/zclconf/go-cty/cty" "golang.org/x/crypto/ssh" ) @@ -125,20 +125,16 @@ func TestNew_Invalid(t *testing.T) { address := newMockLineServer(t, nil, testClientPublicKey) parts := strings.Split(address, ":") - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "user", - "password": "i-am-invalid", - "host": parts[0], - "port": parts[1], - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("user"), + "password": cty.StringVal("i-am-invalid"), + "host": cty.StringVal(parts[0]), + "port": cty.StringVal(parts[1]), + "timeout": cty.StringVal("30s"), + }) - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -150,19 +146,15 @@ func TestNew_Invalid(t *testing.T) { } func TestNew_InvalidHost(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "user", - "password": "i-am-invalid", - "port": "22", - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("user"), + "password": cty.StringVal("i-am-invalid"), + "port": cty.StringVal("22"), + "timeout": cty.StringVal("30s"), + }) - _, err := New(r) + _, err := New(v) if err == nil { t.Fatal("should have had an error creating communicator") } @@ -172,20 +164,16 @@ func TestStart(t *testing.T) { address := newMockLineServer(t, nil, testClientPublicKey) parts := strings.Split(address, ":") - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "user", - "password": "pass", - "host": parts[0], - "port": parts[1], - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("user"), + "password": cty.StringVal("pass"), + "host": cty.StringVal(parts[0]), + "port": cty.StringVal(parts[1]), + "timeout": cty.StringVal("30s"), + }) - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -211,19 +199,15 @@ func TestKeepAlives(t *testing.T) { address := newMockLineServer(t, nil, testClientPublicKey) parts := strings.Split(address, ":") - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "user", - "password": "pass", - "host": parts[0], - "port": parts[1], - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("user"), + "password": cty.StringVal("pass"), + "host": cty.StringVal(parts[0]), + "port": cty.StringVal(parts[1]), + }) - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -261,19 +245,16 @@ func TestFailedKeepAlives(t *testing.T) { address := newMockLineServer(t, nil, testClientPublicKey) parts := strings.Split(address, ":") - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "user", - "password": "pass", - "host": parts[0], - "port": parts[1], - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("user"), + "password": cty.StringVal("pass"), + "host": cty.StringVal(parts[0]), + "port": cty.StringVal(parts[1]), + "timeout": cty.StringVal("30s"), + }) - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -296,20 +277,16 @@ func TestLostConnection(t *testing.T) { address := newMockLineServer(t, nil, testClientPublicKey) parts := strings.Split(address, ":") - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "user", - "password": "pass", - "host": parts[0], - "port": parts[1], - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("user"), + "password": cty.StringVal("pass"), + "host": cty.StringVal(parts[0]), + "port": cty.StringVal(parts[1]), + "timeout": cty.StringVal("30s"), + }) - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -586,19 +563,15 @@ func TestAccUploadFile(t *testing.T) { t.Skip() } - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": os.Getenv("USER"), - "host": "127.0.0.1", - "port": "22", - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal(os.Getenv("USER")), + "host": cty.StringVal("127.0.0.1"), + "port": cty.StringVal("22"), + "timeout": cty.StringVal("30s"), + }) - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -634,19 +607,15 @@ func TestAccHugeUploadFile(t *testing.T) { t.Skip() } - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": os.Getenv("USER"), - "host": "127.0.0.1", - "port": "22", - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "host": cty.StringVal("127.0.0.1"), + "user": cty.StringVal(os.Getenv("USER")), + "port": cty.StringVal("22"), + "timeout": cty.StringVal("30s"), + }) - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -706,16 +675,13 @@ func TestScriptPath(t *testing.T) { } for _, tc := range cases { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "host": "127.0.0.1", - "script_path": tc.Input, - }, - }, - } - comm, err := New(r) + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "host": cty.StringVal("127.0.0.1"), + "script_path": cty.StringVal(tc.Input), + }) + + comm, err := New(v) if err != nil { t.Fatalf("err: %s", err) } @@ -735,14 +701,10 @@ func TestScriptPath_randSeed(t *testing.T) { // Pre GH-4186 fix, this value was the deterministic start the pseudorandom // chain of unseeded math/rand values for Int31(). staticSeedPath := "/tmp/terraform_1298498081.sh" - c, err := New(&terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "host": "127.0.0.1", - }, - }, - }) + c, err := New(cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "host": cty.StringVal("127.0.0.1"), + })) if err != nil { t.Fatalf("err: %s", err) } diff --git a/communicator/ssh/provisioner.go b/communicator/ssh/provisioner.go index ad16053c1..1668da0df 100644 --- a/communicator/ssh/provisioner.go +++ b/communicator/ssh/provisioner.go @@ -10,13 +10,13 @@ import ( "net" "os" "path/filepath" + "strconv" "strings" "time" "github.com/hashicorp/terraform/communicator/shared" - "github.com/hashicorp/terraform/internal/legacy/terraform" - "github.com/mitchellh/mapstructure" sshagent "github.com/xanzy/ssh-agent" + "github.com/zclconf/go-cty/cty" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "golang.org/x/crypto/ssh/knownhosts" @@ -51,41 +51,104 @@ const ( type connectionInfo struct { User string Password string - PrivateKey string `mapstructure:"private_key"` - Certificate string `mapstructure:"certificate"` + PrivateKey string + Certificate string Host string - HostKey string `mapstructure:"host_key"` + HostKey string Port int Agent bool + ScriptPath string + TargetPlatform string Timeout string - ScriptPath string `mapstructure:"script_path"` - TimeoutVal time.Duration `mapstructure:"-"` - TargetPlatform string `mapstructure:"target_platform"` + TimeoutVal time.Duration - BastionUser string `mapstructure:"bastion_user"` - BastionPassword string `mapstructure:"bastion_password"` - BastionPrivateKey string `mapstructure:"bastion_private_key"` - BastionCertificate string `mapstructure:"bastion_certificate"` - BastionHost string `mapstructure:"bastion_host"` - BastionHostKey string `mapstructure:"bastion_host_key"` - BastionPort int `mapstructure:"bastion_port"` + BastionUser string + BastionPassword string + BastionPrivateKey string + BastionCertificate string + BastionHost string + BastionHostKey string + BastionPort int - AgentIdentity string `mapstructure:"agent_identity"` + AgentIdentity string } -// parseConnectionInfo is used to convert the ConnInfo of the InstanceState into -// a ConnectionInfo struct -func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) { +// decodeConnInfo decodes the given cty.Value using the same behavior as the +// lgeacy mapstructure decoder in order to preserve as much of the existing +// logic as possible for compatibility. +func decodeConnInfo(v cty.Value) (*connectionInfo, error) { connInfo := &connectionInfo{} - decConf := &mapstructure.DecoderConfig{ - WeaklyTypedInput: true, - Result: connInfo, + if v.IsNull() { + return connInfo, nil } - dec, err := mapstructure.NewDecoder(decConf) + + for k, v := range v.AsValueMap() { + if v.IsNull() { + continue + } + + switch k { + case "user": + connInfo.User = v.AsString() + case "password": + connInfo.Password = v.AsString() + case "private_key": + connInfo.PrivateKey = v.AsString() + case "certificate": + connInfo.Certificate = v.AsString() + case "host": + connInfo.Host = v.AsString() + case "host_key": + connInfo.HostKey = v.AsString() + case "port": + p, err := strconv.Atoi(v.AsString()) + if err != nil { + return nil, err + } + connInfo.Port = p + case "agent": + connInfo.Agent = v.True() + case "script_path": + connInfo.ScriptPath = v.AsString() + case "target_platform": + connInfo.TargetPlatform = v.AsString() + case "timeout": + connInfo.Timeout = v.AsString() + case "bastion_user": + connInfo.BastionUser = v.AsString() + case "bastion_password": + connInfo.BastionPassword = v.AsString() + case "bastion_private_key": + connInfo.BastionPrivateKey = v.AsString() + case "bastion_certificate": + connInfo.BastionCertificate = v.AsString() + case "bastion_host": + connInfo.BastionHost = v.AsString() + case "bastion_host_key": + connInfo.BastionHostKey = v.AsString() + case "bastion_port": + p, err := strconv.Atoi(v.AsString()) + if err != nil { + return nil, err + } + connInfo.BastionPort = p + case "agent_identity": + connInfo.AgentIdentity = v.AsString() + } + } + return connInfo, nil +} + +// parseConnectionInfo is used to convert the raw configuration into the +// *connectionInfo struct. +func parseConnectionInfo(v cty.Value) (*connectionInfo, error) { + v, err := shared.ConnectionBlockSupersetSchema.CoerceValue(v) if err != nil { return nil, err } - if err := dec.Decode(s.Ephemeral.ConnInfo); err != nil { + + connInfo, err := decodeConnInfo(v) + if err != nil { return nil, err } @@ -94,7 +157,8 @@ func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) { // // And if SSH_AUTH_SOCK is not set, there's no agent to connect to, so we // shouldn't try. - if s.Ephemeral.ConnInfo["agent"] == "" && os.Getenv("SSH_AUTH_SOCK") != "" { + agent := v.GetAttr("agent") + if agent.IsNull() && os.Getenv("SSH_AUTH_SOCK") != "" { connInfo.Agent = true } diff --git a/communicator/ssh/provisioner_test.go b/communicator/ssh/provisioner_test.go index 99f06d920..ace46f49d 100644 --- a/communicator/ssh/provisioner_test.go +++ b/communicator/ssh/provisioner_test.go @@ -3,28 +3,23 @@ package ssh import ( "testing" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/zclconf/go-cty/cty" ) func TestProvisioner_connInfo(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "root", - "password": "supersecret", - "private_key": "someprivatekeycontents", - "certificate": "somecertificate", - "host": "127.0.0.1", - "port": "22", - "timeout": "30s", + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("root"), + "password": cty.StringVal("supersecret"), + "private_key": cty.StringVal("someprivatekeycontents"), + "certificate": cty.StringVal("somecertificate"), + "host": cty.StringVal("127.0.0.1"), + "port": cty.StringVal("22"), + "timeout": cty.StringVal("30s"), + "bastion_host": cty.StringVal("127.0.1.1"), + }) - "bastion_host": "127.0.1.1", - }, - }, - } - - conf, err := parseConnectionInfo(r) + conf, err := parseConnectionInfo(v) if err != nil { t.Fatalf("err: %v", err) } @@ -74,24 +69,18 @@ func TestProvisioner_connInfo(t *testing.T) { } func TestProvisioner_connInfoIpv6(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "root", - "password": "supersecret", - "private_key": "someprivatekeycontents", - "certificate": "somecertificate", - "host": "::1", - "port": "22", - "timeout": "30s", + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("root"), + "password": cty.StringVal("supersecret"), + "private_key": cty.StringVal("someprivatekeycontents"), + "host": cty.StringVal("::1"), + "port": cty.StringVal("22"), + "timeout": cty.StringVal("30s"), + "bastion_host": cty.StringVal("::1"), + }) - "bastion_host": "::1", - }, - }, - } - - conf, err := parseConnectionInfo(r) + conf, err := parseConnectionInfo(v) if err != nil { t.Fatalf("err: %v", err) } @@ -106,22 +95,18 @@ func TestProvisioner_connInfoIpv6(t *testing.T) { } func TestProvisioner_connInfoHostname(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "root", - "password": "supersecret", - "private_key": "someprivatekeycontents", - "host": "example.com", - "port": "22", - "timeout": "30s", - "bastion_host": "example.com", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("root"), + "password": cty.StringVal("supersecret"), + "private_key": cty.StringVal("someprivatekeycontents"), + "host": cty.StringVal("example.com"), + "port": cty.StringVal("22"), + "timeout": cty.StringVal("30s"), + "bastion_host": cty.StringVal("example.com"), + }) - conf, err := parseConnectionInfo(r) + conf, err := parseConnectionInfo(v) if err != nil { t.Fatalf("err: %v", err) } @@ -136,21 +121,16 @@ func TestProvisioner_connInfoHostname(t *testing.T) { } func TestProvisioner_connInfoEmptyHostname(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "root", - "password": "supersecret", - "private_key": "someprivatekeycontents", - "host": "", - "port": "22", - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "user": cty.StringVal("root"), + "password": cty.StringVal("supersecret"), + "private_key": cty.StringVal("someprivatekeycontents"), + "port": cty.StringVal("22"), + "timeout": cty.StringVal("30s"), + }) - _, err := parseConnectionInfo(r) + _, err := parseConnectionInfo(v) if err == nil { t.Fatalf("bad: should not allow empty host") } diff --git a/communicator/winrm/communicator.go b/communicator/winrm/communicator.go index 1489d514c..4f9f28838 100644 --- a/communicator/winrm/communicator.go +++ b/communicator/winrm/communicator.go @@ -10,9 +10,10 @@ import ( "time" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/provisioners" "github.com/masterzen/winrm" "github.com/packer-community/winrmcp/winrmcp" + "github.com/zclconf/go-cty/cty" ) // Communicator represents the WinRM communicator @@ -24,8 +25,8 @@ type Communicator struct { } // New creates a new communicator implementation over WinRM. -func New(s *terraform.InstanceState) (*Communicator, error) { - connInfo, err := parseConnectionInfo(s) +func New(v cty.Value) (*Communicator, error) { + connInfo, err := parseConnectionInfo(v) if err != nil { return nil, err } @@ -52,7 +53,7 @@ func New(s *terraform.InstanceState) (*Communicator, error) { } // Connect implementation of communicator.Communicator interface -func (c *Communicator) Connect(o terraform.UIOutput) error { +func (c *Communicator) Connect(o provisioners.UIOutput) error { // Set the client to nil since we'll (re)create it c.client = nil diff --git a/communicator/winrm/communicator_test.go b/communicator/winrm/communicator_test.go index 62d4fab55..bd8d2ecd5 100644 --- a/communicator/winrm/communicator_test.go +++ b/communicator/winrm/communicator_test.go @@ -9,7 +9,8 @@ import ( "github.com/dylanmei/winrmtest" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/hashicorp/terraform/communicator/shared" + "github.com/zclconf/go-cty/cty" ) func newMockWinRMServer(t *testing.T) *winrmtest.Remote { @@ -47,20 +48,16 @@ func TestStart(t *testing.T) { wrm := newMockWinRMServer(t) defer wrm.Close() - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "user": "user", - "password": "pass", - "host": wrm.Host, - "port": strconv.Itoa(wrm.Port), - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "user": cty.StringVal("user"), + "password": cty.StringVal("pass"), + "host": cty.StringVal(wrm.Host), + "port": cty.StringVal(strconv.Itoa(wrm.Port)), + "timeout": cty.StringVal("30s"), + }) - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -84,21 +81,16 @@ func TestStart(t *testing.T) { func TestUpload(t *testing.T) { wrm := newMockWinRMServer(t) defer wrm.Close() + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "user": cty.StringVal("user"), + "password": cty.StringVal("pass"), + "host": cty.StringVal(wrm.Host), + "port": cty.StringVal(strconv.Itoa(wrm.Port)), + "timeout": cty.StringVal("30s"), + }) - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "user": "user", - "password": "pass", - "host": wrm.Host, - "port": strconv.Itoa(wrm.Port), - "timeout": "30s", - }, - }, - } - - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -131,15 +123,13 @@ func TestScriptPath(t *testing.T) { } for _, tc := range cases { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "script_path": tc.Input, - }, - }, - } - comm, err := New(r) + v := cty.ObjectVal(map[string]cty.Value{ + "host": cty.StringVal(""), + "type": cty.StringVal("winrm"), + "script_path": cty.StringVal(tc.Input), + }) + + comm, err := New(v) if err != nil { t.Fatalf("err: %s", err) } @@ -158,21 +148,16 @@ func TestScriptPath(t *testing.T) { func TestNoTransportDecorator(t *testing.T) { wrm := newMockWinRMServer(t) defer wrm.Close() + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "user": cty.StringVal("user"), + "password": cty.StringVal("pass"), + "host": cty.StringVal(wrm.Host), + "port": cty.StringVal(strconv.Itoa(wrm.Port)), + "timeout": cty.StringVal("30s"), + }) - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "user": "user", - "password": "pass", - "host": wrm.Host, - "port": strconv.Itoa(wrm.Port), - "timeout": "30s", - }, - }, - } - - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -192,21 +177,17 @@ func TestTransportDecorator(t *testing.T) { wrm := newMockWinRMServer(t) defer wrm.Close() - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "user": "user", - "password": "pass", - "host": wrm.Host, - "port": strconv.Itoa(wrm.Port), - "use_ntlm": "true", - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "user": cty.StringVal("user"), + "password": cty.StringVal("pass"), + "host": cty.StringVal(wrm.Host), + "port": cty.StringVal(strconv.Itoa(wrm.Port)), + "use_ntlm": cty.StringVal("true"), + "timeout": cty.StringVal("30s"), + }) - c, err := New(r) + c, err := New(v) if err != nil { t.Fatalf("error creating communicator: %s", err) } @@ -226,7 +207,7 @@ func TestScriptPath_randSeed(t *testing.T) { // Pre GH-4186 fix, this value was the deterministic start the pseudorandom // chain of unseeded math/rand values for Int31(). staticSeedPath := "C:/Temp/terraform_1298498081.cmd" - c, err := New(&terraform.InstanceState{}) + c, err := New(cty.NullVal(shared.ConnectionBlockSupersetSchema.ImpliedType())) if err != nil { t.Fatalf("err: %s", err) } diff --git a/communicator/winrm/provisioner.go b/communicator/winrm/provisioner.go index df3ed89a4..7a71fe92f 100644 --- a/communicator/winrm/provisioner.go +++ b/communicator/winrm/provisioner.go @@ -4,12 +4,12 @@ import ( "fmt" "log" "path/filepath" + "strconv" "strings" "time" "github.com/hashicorp/terraform/communicator/shared" - "github.com/hashicorp/terraform/internal/legacy/terraform" - "github.com/mitchellh/mapstructure" + "github.com/zclconf/go-cty/cty" ) const ( @@ -47,22 +47,62 @@ type connectionInfo struct { TimeoutVal time.Duration `mapstructure:"-"` } +// decodeConnInfo decodes the given cty.Value using the same behavior as the +// lgeacy mapstructure decoder in order to preserve as much of the existing +// logic as possible for compatibility. +func decodeConnInfo(v cty.Value) (*connectionInfo, error) { + connInfo := &connectionInfo{} + if v.IsNull() { + return connInfo, nil + } + + for k, v := range v.AsValueMap() { + if v.IsNull() { + continue + } + + switch k { + case "user": + connInfo.User = v.AsString() + case "password": + connInfo.Password = v.AsString() + case "host": + connInfo.Host = v.AsString() + case "port": + p, err := strconv.Atoi(v.AsString()) + if err != nil { + return nil, err + } + connInfo.Port = p + case "https": + connInfo.HTTPS = v.True() + case "insecure": + connInfo.Insecure = v.True() + case "use_ntlm": + connInfo.NTLM = v.True() + case "cacert": + connInfo.CACert = v.AsString() + case "script_path": + connInfo.ScriptPath = v.AsString() + case "timeout": + connInfo.Timeout = v.AsString() + } + } + return connInfo, nil +} + // parseConnectionInfo is used to convert the ConnInfo of the InstanceState into // a ConnectionInfo struct -func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) { - connInfo := &connectionInfo{} - decConf := &mapstructure.DecoderConfig{ - WeaklyTypedInput: true, - Result: connInfo, - } - dec, err := mapstructure.NewDecoder(decConf) +func parseConnectionInfo(v cty.Value) (*connectionInfo, error) { + v, err := shared.ConnectionBlockSupersetSchema.CoerceValue(v) if err != nil { return nil, err } - if err := dec.Decode(s.Ephemeral.ConnInfo); err != nil { + + connInfo, err := decodeConnInfo(v) + if err != nil { return nil, err } - // Check on script paths which point to the default Windows TEMP folder because files // which are put in there very early in the boot process could get cleaned/deleted // before you had the change to execute them. diff --git a/communicator/winrm/provisioner_test.go b/communicator/winrm/provisioner_test.go index 40f59c55f..50718aa86 100644 --- a/communicator/winrm/provisioner_test.go +++ b/communicator/winrm/provisioner_test.go @@ -3,23 +3,19 @@ package winrm import ( "testing" - "github.com/hashicorp/terraform/internal/legacy/terraform" + "github.com/zclconf/go-cty/cty" ) func TestProvisioner_defaultHTTPSPort(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "user": "Administrator", - "password": "supersecret", - "host": "127.0.0.1", - "https": "true", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "user": cty.StringVal("Administrator"), + "password": cty.StringVal("supersecret"), + "host": cty.StringVal("127.0.0.1"), + "https": cty.True, + }) - conf, err := parseConnectionInfo(r) + conf, err := parseConnectionInfo(v) if err != nil { t.Fatalf("err: %v", err) } @@ -32,22 +28,18 @@ func TestProvisioner_defaultHTTPSPort(t *testing.T) { } func TestProvisioner_connInfo(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "user": "Administrator", - "password": "supersecret", - "host": "127.0.0.1", - "port": "5985", - "https": "true", - "use_ntlm": "true", - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "user": cty.StringVal("Administrator"), + "password": cty.StringVal("supersecret"), + "host": cty.StringVal("127.0.0.1"), + "port": cty.StringVal("5985"), + "https": cty.True, + "use_ntlm": cty.True, + "timeout": cty.StringVal("30s"), + }) - conf, err := parseConnectionInfo(r) + conf, err := parseConnectionInfo(v) if err != nil { t.Fatalf("err: %v", err) } @@ -100,23 +92,18 @@ CqDUFjhydXxYRsxXBBrEiLOE5BdtJR1sH/QHxIJe23C9iHI2nS1NbLziNEApLwC4 GnSud83VUo9G9w== -----END CERTIFICATE----- ` + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "user": cty.StringVal("Administrator"), + "password": cty.StringVal("supersecret"), + "host": cty.StringVal("127.0.0.1"), + "port": cty.StringVal("5985"), + "https": cty.True, + "timeout": cty.StringVal("30s"), + "cacert": cty.StringVal(caCert), + }) - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "user": "Administrator", - "password": "supersecret", - "host": "127.0.0.1", - "port": "5985", - "https": "true", - "timeout": "30s", - "cacert": caCert, - }, - }, - } - - conf, err := parseConnectionInfo(r) + conf, err := parseConnectionInfo(v) if err != nil { t.Fatalf("err: %v", err) } @@ -148,21 +135,17 @@ GnSud83VUo9G9w== } func TestProvisioner_connInfoIpv6(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "user": "Administrator", - "password": "supersecret", - "host": "::1", - "port": "5985", - "https": "true", - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "user": cty.StringVal("Administrator"), + "password": cty.StringVal("supersecret"), + "host": cty.StringVal("::1"), + "port": cty.StringVal("5985"), + "https": cty.True, + "timeout": cty.StringVal("30s"), + }) - conf, err := parseConnectionInfo(r) + conf, err := parseConnectionInfo(v) if err != nil { t.Fatalf("err: %v", err) } @@ -191,21 +174,17 @@ func TestProvisioner_connInfoIpv6(t *testing.T) { } func TestProvisioner_connInfoHostname(t *testing.T) { - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "winrm", - "user": "Administrator", - "password": "supersecret", - "host": "example.com", - "port": "5985", - "https": "true", - "timeout": "30s", - }, - }, - } + v := cty.ObjectVal(map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "user": cty.StringVal("Administrator"), + "password": cty.StringVal("supersecret"), + "host": cty.StringVal("example.com"), + "port": cty.StringVal("5985"), + "https": cty.True, + "timeout": cty.StringVal("30s"), + }) - conf, err := parseConnectionInfo(r) + conf, err := parseConnectionInfo(v) if err != nil { t.Fatalf("err: %v", err) } @@ -235,38 +214,26 @@ func TestProvisioner_connInfoHostname(t *testing.T) { func TestProvisioner_formatDuration(t *testing.T) { cases := map[string]struct { - InstanceState *terraform.InstanceState - Result string + Config map[string]cty.Value + Result string }{ "testSeconds": { - InstanceState: &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "timeout": "90s", - }, - }, + Config: map[string]cty.Value{ + "timeout": cty.StringVal("90s"), }, Result: "PT1M30S", }, "testMinutes": { - InstanceState: &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "timeout": "5m", - }, - }, + Config: map[string]cty.Value{ + "timeout": cty.StringVal("5m"), }, Result: "PT5M", }, "testHours": { - InstanceState: &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "timeout": "1h", - }, - }, + Config: map[string]cty.Value{ + "timeout": cty.StringVal("1h"), }, Result: "PT1H", @@ -274,7 +241,10 @@ func TestProvisioner_formatDuration(t *testing.T) { } for name, tc := range cases { - conf, err := parseConnectionInfo(tc.InstanceState) + // host is required in the schema + tc.Config["host"] = cty.StringVal("") + + conf, err := parseConnectionInfo(cty.ObjectVal(tc.Config)) if err != nil { t.Fatalf("err: %v", err) } diff --git a/internal/grpcwrap/provider.go b/internal/grpcwrap/provider.go new file mode 100644 index 000000000..6d7da068d --- /dev/null +++ b/internal/grpcwrap/provider.go @@ -0,0 +1,415 @@ +package grpcwrap + +import ( + "context" + + "github.com/hashicorp/terraform/internal/tfplugin5" + "github.com/hashicorp/terraform/plugin/convert" + "github.com/hashicorp/terraform/providers" + "github.com/zclconf/go-cty/cty" + ctyjson "github.com/zclconf/go-cty/cty/json" + "github.com/zclconf/go-cty/cty/msgpack" +) + +// New wraps a providers.Interface to implement a grpc ProviderServer. +// This is useful for creating a test binary out of an internal provider +// implementation. +func Provider(p providers.Interface) tfplugin5.ProviderServer { + return &provider{ + provider: p, + schema: p.GetSchema(), + } +} + +type provider struct { + provider providers.Interface + schema providers.GetSchemaResponse +} + +func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema_Request) (*tfplugin5.GetProviderSchema_Response, error) { + resp := &tfplugin5.GetProviderSchema_Response{ + ResourceSchemas: make(map[string]*tfplugin5.Schema), + DataSourceSchemas: make(map[string]*tfplugin5.Schema), + } + + resp.Provider = &tfplugin5.Schema{ + Block: &tfplugin5.Schema_Block{}, + } + if p.schema.Provider.Block != nil { + resp.Provider.Block = convert.ConfigSchemaToProto(p.schema.Provider.Block) + } + + resp.ProviderMeta = &tfplugin5.Schema{ + Block: &tfplugin5.Schema_Block{}, + } + if p.schema.ProviderMeta.Block != nil { + resp.ProviderMeta.Block = convert.ConfigSchemaToProto(p.schema.ProviderMeta.Block) + } + + for typ, res := range p.schema.ResourceTypes { + resp.ResourceSchemas[typ] = &tfplugin5.Schema{ + Version: res.Version, + Block: convert.ConfigSchemaToProto(res.Block), + } + } + for typ, dat := range p.schema.DataSources { + resp.DataSourceSchemas[typ] = &tfplugin5.Schema{ + Version: dat.Version, + Block: convert.ConfigSchemaToProto(dat.Block), + } + } + + // include any diagnostics from the original GetSchema call + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, p.schema.Diagnostics) + + return resp, nil +} + +func (p *provider) PrepareProviderConfig(_ context.Context, req *tfplugin5.PrepareProviderConfig_Request) (*tfplugin5.PrepareProviderConfig_Response, error) { + resp := &tfplugin5.PrepareProviderConfig_Response{} + ty := p.schema.Provider.Block.ImpliedType() + + configVal, err := decodeDynamicValue(req.Config, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + prepareResp := p.provider.PrepareProviderConfig(providers.PrepareProviderConfigRequest{ + Config: configVal, + }) + + // the PreparedConfig value is no longer used + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, prepareResp.Diagnostics) + return resp, nil +} + +func (p *provider) ValidateResourceTypeConfig(_ context.Context, req *tfplugin5.ValidateResourceTypeConfig_Request) (*tfplugin5.ValidateResourceTypeConfig_Response, error) { + resp := &tfplugin5.ValidateResourceTypeConfig_Response{} + ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType() + + configVal, err := decodeDynamicValue(req.Config, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + validateResp := p.provider.ValidateResourceTypeConfig(providers.ValidateResourceTypeConfigRequest{ + TypeName: req.TypeName, + Config: configVal, + }) + + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics) + return resp, nil +} + +func (p *provider) ValidateDataSourceConfig(_ context.Context, req *tfplugin5.ValidateDataSourceConfig_Request) (*tfplugin5.ValidateDataSourceConfig_Response, error) { + resp := &tfplugin5.ValidateDataSourceConfig_Response{} + ty := p.schema.DataSources[req.TypeName].Block.ImpliedType() + + configVal, err := decodeDynamicValue(req.Config, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + validateResp := p.provider.ValidateDataSourceConfig(providers.ValidateDataSourceConfigRequest{ + TypeName: req.TypeName, + Config: configVal, + }) + + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics) + return resp, nil +} + +func (p *provider) UpgradeResourceState(_ context.Context, req *tfplugin5.UpgradeResourceState_Request) (*tfplugin5.UpgradeResourceState_Response, error) { + resp := &tfplugin5.UpgradeResourceState_Response{} + ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType() + + upgradeResp := p.provider.UpgradeResourceState(providers.UpgradeResourceStateRequest{ + TypeName: req.TypeName, + Version: req.Version, + RawStateJSON: req.RawState.Json, + }) + + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, upgradeResp.Diagnostics) + if upgradeResp.Diagnostics.HasErrors() { + return resp, nil + } + + dv, err := encodeDynamicValue(upgradeResp.UpgradedState, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + resp.UpgradedState = dv + + return resp, nil +} + +func (p *provider) Configure(_ context.Context, req *tfplugin5.Configure_Request) (*tfplugin5.Configure_Response, error) { + resp := &tfplugin5.Configure_Response{} + ty := p.schema.Provider.Block.ImpliedType() + + configVal, err := decodeDynamicValue(req.Config, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + configureResp := p.provider.Configure(providers.ConfigureRequest{ + TerraformVersion: req.TerraformVersion, + Config: configVal, + }) + + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, configureResp.Diagnostics) + return resp, nil +} + +func (p *provider) ReadResource(_ context.Context, req *tfplugin5.ReadResource_Request) (*tfplugin5.ReadResource_Response, error) { + resp := &tfplugin5.ReadResource_Response{} + ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType() + + stateVal, err := decodeDynamicValue(req.CurrentState, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + metaTy := p.schema.ProviderMeta.Block.ImpliedType() + metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + readResp := p.provider.ReadResource(providers.ReadResourceRequest{ + TypeName: req.TypeName, + PriorState: stateVal, + Private: req.Private, + ProviderMeta: metaVal, + }) + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics) + if readResp.Diagnostics.HasErrors() { + return resp, nil + } + resp.Private = readResp.Private + + dv, err := encodeDynamicValue(readResp.NewState, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + resp.NewState = dv + + return resp, nil +} + +func (p *provider) PlanResourceChange(_ context.Context, req *tfplugin5.PlanResourceChange_Request) (*tfplugin5.PlanResourceChange_Response, error) { + resp := &tfplugin5.PlanResourceChange_Response{} + ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType() + + priorStateVal, err := decodeDynamicValue(req.PriorState, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + proposedStateVal, err := decodeDynamicValue(req.ProposedNewState, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + configVal, err := decodeDynamicValue(req.Config, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + metaTy := p.schema.ProviderMeta.Block.ImpliedType() + metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + planResp := p.provider.PlanResourceChange(providers.PlanResourceChangeRequest{ + TypeName: req.TypeName, + PriorState: priorStateVal, + ProposedNewState: proposedStateVal, + Config: configVal, + PriorPrivate: req.PriorPrivate, + ProviderMeta: metaVal, + }) + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics) + if planResp.Diagnostics.HasErrors() { + return resp, nil + } + + resp.PlannedPrivate = planResp.PlannedPrivate + + resp.PlannedState, err = encodeDynamicValue(planResp.PlannedState, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + for _, path := range planResp.RequiresReplace { + resp.RequiresReplace = append(resp.RequiresReplace, convert.PathToAttributePath(path)) + } + + return resp, nil +} + +func (p *provider) ApplyResourceChange(_ context.Context, req *tfplugin5.ApplyResourceChange_Request) (*tfplugin5.ApplyResourceChange_Response, error) { + resp := &tfplugin5.ApplyResourceChange_Response{} + ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType() + + priorStateVal, err := decodeDynamicValue(req.PriorState, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + plannedStateVal, err := decodeDynamicValue(req.PlannedState, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + configVal, err := decodeDynamicValue(req.Config, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + metaTy := p.schema.ProviderMeta.Block.ImpliedType() + metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + applyResp := p.provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{ + TypeName: req.TypeName, + PriorState: priorStateVal, + PlannedState: plannedStateVal, + Config: configVal, + PlannedPrivate: req.PlannedPrivate, + ProviderMeta: metaVal, + }) + + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, applyResp.Diagnostics) + if applyResp.Diagnostics.HasErrors() { + return resp, nil + } + resp.Private = applyResp.Private + + resp.NewState, err = encodeDynamicValue(applyResp.NewState, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + return resp, nil +} + +func (p *provider) ImportResourceState(_ context.Context, req *tfplugin5.ImportResourceState_Request) (*tfplugin5.ImportResourceState_Response, error) { + resp := &tfplugin5.ImportResourceState_Response{} + + importResp := p.provider.ImportResourceState(providers.ImportResourceStateRequest{ + TypeName: req.TypeName, + ID: req.Id, + }) + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, importResp.Diagnostics) + + for _, res := range importResp.ImportedResources { + ty := p.schema.ResourceTypes[res.TypeName].Block.ImpliedType() + state, err := encodeDynamicValue(res.State, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + continue + } + + resp.ImportedResources = append(resp.ImportedResources, &tfplugin5.ImportResourceState_ImportedResource{ + TypeName: res.TypeName, + State: state, + Private: res.Private, + }) + } + + return resp, nil +} + +func (p *provider) ReadDataSource(_ context.Context, req *tfplugin5.ReadDataSource_Request) (*tfplugin5.ReadDataSource_Response, error) { + resp := &tfplugin5.ReadDataSource_Response{} + ty := p.schema.DataSources[req.TypeName].Block.ImpliedType() + + configVal, err := decodeDynamicValue(req.Config, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + metaTy := p.schema.ProviderMeta.Block.ImpliedType() + metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + readResp := p.provider.ReadDataSource(providers.ReadDataSourceRequest{ + TypeName: req.TypeName, + Config: configVal, + ProviderMeta: metaVal, + }) + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics) + if readResp.Diagnostics.HasErrors() { + return resp, nil + } + + resp.State, err = encodeDynamicValue(readResp.State, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + return resp, nil +} + +func (p *provider) Stop(context.Context, *tfplugin5.Stop_Request) (*tfplugin5.Stop_Response, error) { + resp := &tfplugin5.Stop_Response{} + err := p.provider.Stop() + if err != nil { + resp.Error = err.Error() + } + return resp, nil +} + +// decode a DynamicValue from either the JSON or MsgPack encoding. +func decodeDynamicValue(v *tfplugin5.DynamicValue, ty cty.Type) (cty.Value, error) { + // always return a valid value + var err error + res := cty.NullVal(ty) + if v == nil { + return res, nil + } + + switch { + case len(v.Msgpack) > 0: + res, err = msgpack.Unmarshal(v.Msgpack, ty) + case len(v.Json) > 0: + res, err = ctyjson.Unmarshal(v.Json, ty) + } + return res, err +} + +// encode a cty.Value into a DynamicValue msgpack payload. +func encodeDynamicValue(v cty.Value, ty cty.Type) (*tfplugin5.DynamicValue, error) { + mp, err := msgpack.Marshal(v, ty) + return &tfplugin5.DynamicValue{ + Msgpack: mp, + }, err +} diff --git a/internal/grpcwrap/provisioner.go b/internal/grpcwrap/provisioner.go new file mode 100644 index 000000000..1fffc40ac --- /dev/null +++ b/internal/grpcwrap/provisioner.go @@ -0,0 +1,116 @@ +package grpcwrap + +import ( + "context" + "log" + "strings" + "unicode/utf8" + + "github.com/hashicorp/terraform/communicator/shared" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/internal/tfplugin5" + "github.com/hashicorp/terraform/plugin/convert" + "github.com/hashicorp/terraform/provisioners" +) + +// New wraps a providers.Interface to implement a grpc ProviderServer. +// This is useful for creating a test binary out of an internal provider +// implementation. +func Provisioner(p provisioners.Interface) tfplugin5.ProvisionerServer { + return &provisioner{ + provisioner: p, + schema: p.GetSchema().Provisioner, + } +} + +type provisioner struct { + provisioner provisioners.Interface + schema *configschema.Block +} + +func (p *provisioner) GetSchema(_ context.Context, req *tfplugin5.GetProvisionerSchema_Request) (*tfplugin5.GetProvisionerSchema_Response, error) { + resp := &tfplugin5.GetProvisionerSchema_Response{} + + resp.Provisioner = &tfplugin5.Schema{ + Block: &tfplugin5.Schema_Block{}, + } + + if p.schema != nil { + resp.Provisioner.Block = convert.ConfigSchemaToProto(p.schema) + } + + return resp, nil +} + +func (p *provisioner) ValidateProvisionerConfig(_ context.Context, req *tfplugin5.ValidateProvisionerConfig_Request) (*tfplugin5.ValidateProvisionerConfig_Response, error) { + resp := &tfplugin5.ValidateProvisionerConfig_Response{} + ty := p.schema.ImpliedType() + + configVal, err := decodeDynamicValue(req.Config, ty) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + validateResp := p.provisioner.ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{ + Config: configVal, + }) + + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics) + return resp, nil +} + +func (p *provisioner) ProvisionResource(req *tfplugin5.ProvisionResource_Request, srv tfplugin5.Provisioner_ProvisionResourceServer) error { + // We send back a diagnostics over the stream if there was a + // provisioner-side problem. + srvResp := &tfplugin5.ProvisionResource_Response{} + + ty := p.schema.ImpliedType() + configVal, err := decodeDynamicValue(req.Config, ty) + if err != nil { + srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err) + srv.Send(srvResp) + return nil + } + + connVal, err := decodeDynamicValue(req.Connection, shared.ConnectionBlockSupersetSchema.ImpliedType()) + if err != nil { + srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err) + srv.Send(srvResp) + return nil + } + + resp := p.provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{ + Config: configVal, + Connection: connVal, + UIOutput: uiOutput{srv}, + }) + + srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, resp.Diagnostics) + srv.Send(srvResp) + return nil +} + +func (p *provisioner) Stop(context.Context, *tfplugin5.Stop_Request) (*tfplugin5.Stop_Response, error) { + resp := &tfplugin5.Stop_Response{} + err := p.provisioner.Stop() + if err != nil { + resp.Error = err.Error() + } + return resp, nil +} + +// uiOutput implements the terraform.UIOutput interface to adapt the grpc +// stream to the legacy Provisioner.Apply method. +type uiOutput struct { + srv tfplugin5.Provisioner_ProvisionResourceServer +} + +func (o uiOutput) Output(s string) { + err := o.srv.Send(&tfplugin5.ProvisionResource_Response{ + Output: strings.ToValidUTF8(s, string(utf8.RuneError)), + }) + if err != nil { + log.Printf("[ERROR] %s", err) + } +} diff --git a/internal/legacy/builtin/bins/provider-test/main.go b/internal/legacy/builtin/bins/provider-test/main.go deleted file mode 100644 index eb6d3f796..000000000 --- a/internal/legacy/builtin/bins/provider-test/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/internal/legacy/builtin/providers/test" - "github.com/hashicorp/terraform/internal/legacy/terraform" - "github.com/hashicorp/terraform/plugin" -) - -func main() { - plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: func() terraform.ResourceProvider { - return test.Provider() - }, - }) -} diff --git a/internal/legacy/builtin/providers/test/data_source.go b/internal/legacy/builtin/providers/test/data_source.go deleted file mode 100644 index 11ff28a2f..000000000 --- a/internal/legacy/builtin/providers/test/data_source.go +++ /dev/null @@ -1,63 +0,0 @@ -package test - -import ( - "time" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testDataSource() *schema.Resource { - return &schema.Resource{ - Read: testDataSourceRead, - - Schema: map[string]*schema.Schema{ - "list": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - - "input": { - Type: schema.TypeString, - Optional: true, - }, - - "output": { - Type: schema.TypeString, - Computed: true, - }, - // this attribute is computed, but never set by the provider - "nil": { - Type: schema.TypeString, - Computed: true, - }, - - "input_map": { - Type: schema.TypeMap, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - }, - "output_map": { - Type: schema.TypeMap, - Elem: &schema.Schema{Type: schema.TypeString}, - Computed: true, - }, - }, - } -} - -func testDataSourceRead(d *schema.ResourceData, meta interface{}) error { - d.SetId(time.Now().UTC().String()) - d.Set("list", []interface{}{"one", "two", "three"}) - - if input, hasInput := d.GetOk("input"); hasInput { - d.Set("output", input) - } else { - d.Set("output", "some output") - } - - if inputMap, hasInput := d.GetOk("input_map"); hasInput { - d.Set("output_map", inputMap) - } - return nil -} diff --git a/internal/legacy/builtin/providers/test/data_source_label.go b/internal/legacy/builtin/providers/test/data_source_label.go deleted file mode 100644 index 8e97d2fed..000000000 --- a/internal/legacy/builtin/providers/test/data_source_label.go +++ /dev/null @@ -1,25 +0,0 @@ -package test - -import ( - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func providerLabelDataSource() *schema.Resource { - return &schema.Resource{ - Read: providerLabelDataSourceRead, - - Schema: map[string]*schema.Schema{ - "label": { - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func providerLabelDataSourceRead(d *schema.ResourceData, meta interface{}) error { - label := meta.(string) - d.SetId(label) - d.Set("label", label) - return nil -} diff --git a/internal/legacy/builtin/providers/test/provider.go b/internal/legacy/builtin/providers/test/provider.go deleted file mode 100644 index 4ca826898..000000000 --- a/internal/legacy/builtin/providers/test/provider.go +++ /dev/null @@ -1,59 +0,0 @@ -package test - -import ( - "github.com/hashicorp/terraform/internal/legacy/helper/schema" - "github.com/hashicorp/terraform/internal/legacy/terraform" -) - -func Provider() terraform.ResourceProvider { - return &schema.Provider{ - Schema: map[string]*schema.Schema{ - // Optional attribute to label a particular instance for a test - // that has multiple instances of this provider, so that they - // can be distinguished using the test_provider_label data source. - "label": { - Type: schema.TypeString, - Optional: true, - }, - }, - ProviderMetaSchema: map[string]*schema.Schema{ - // Optionally allow specifying information at a module-level - "foo": { - Type: schema.TypeString, - Optional: true, - }, - }, - ResourcesMap: map[string]*schema.Resource{ - "test_resource": testResource(), - "test_resource_gh12183": testResourceGH12183(), - "test_resource_with_custom_diff": testResourceCustomDiff(), - "test_resource_timeout": testResourceTimeout(), - "test_resource_diff_suppress": testResourceDiffSuppress(), - "test_resource_force_new": testResourceForceNew(), - "test_resource_nested": testResourceNested(), - "test_resource_nested_set": testResourceNestedSet(), - "test_resource_state_func": testResourceStateFunc(), - "test_resource_deprecated": testResourceDeprecated(), - "test_resource_defaults": testResourceDefaults(), - "test_resource_list": testResourceList(), - "test_resource_list_set": testResourceListSet(), - "test_resource_map": testResourceMap(), - "test_resource_computed_set": testResourceComputedSet(), - "test_resource_config_mode": testResourceConfigMode(), - "test_resource_nested_id": testResourceNestedId(), - "test_resource_provider_meta": testResourceProviderMeta(), - "test_resource_signal": testResourceSignal(), - "test_undeleteable": testResourceUndeleteable(), - "test_resource_required_min": testResourceRequiredMin(), - }, - DataSourcesMap: map[string]*schema.Resource{ - "test_data_source": testDataSource(), - "test_provider_label": providerLabelDataSource(), - }, - ConfigureFunc: providerConfigure, - } -} - -func providerConfigure(d *schema.ResourceData) (interface{}, error) { - return d.Get("label"), nil -} diff --git a/internal/legacy/builtin/providers/test/resource.go b/internal/legacy/builtin/providers/test/resource.go deleted file mode 100644 index 46b3a1cb5..000000000 --- a/internal/legacy/builtin/providers/test/resource.go +++ /dev/null @@ -1,233 +0,0 @@ -package test - -import ( - "errors" - "fmt" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResource() *schema.Resource { - return &schema.Resource{ - Create: testResourceCreate, - Read: testResourceRead, - Update: testResourceUpdate, - Delete: testResourceDelete, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - CustomizeDiff: func(d *schema.ResourceDiff, _ interface{}) error { - if d.HasChange("optional") { - d.SetNewComputed("planned_computed") - } - return nil - }, - - Schema: map[string]*schema.Schema{ - "required": { - Type: schema.TypeString, - Required: true, - }, - "optional": { - Type: schema.TypeString, - Optional: true, - }, - "optional_bool": { - Type: schema.TypeBool, - Optional: true, - }, - "optional_force_new": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "optional_computed_map": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - }, - "optional_computed_force_new": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - "optional_computed": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "computed_read_only": { - Type: schema.TypeString, - Computed: true, - }, - "computed_from_required": { - Type: schema.TypeString, - Computed: true, - ForceNew: true, - }, - "computed_read_only_force_new": { - Type: schema.TypeString, - Computed: true, - ForceNew: true, - }, - "computed_list": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "set": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Set: schema.HashString, - }, - "computed_set": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Set: schema.HashString, - }, - "map": { - Type: schema.TypeMap, - Optional: true, - }, - "optional_map": { - Type: schema.TypeMap, - Optional: true, - }, - "required_map": { - Type: schema.TypeMap, - Required: true, - }, - "map_that_look_like_set": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "computed_map": { - Type: schema.TypeMap, - Computed: true, - }, - "list": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "list_of_map": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeMap, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - "apply_error": { - Type: schema.TypeString, - Optional: true, - Description: "return and error during apply", - }, - "planned_computed": { - Type: schema.TypeString, - Computed: true, - Description: "copied the required field during apply, and plans computed when changed", - }, - // this should return unset from GetOkExists - "get_ok_exists_false": { - Type: schema.TypeBool, - Computed: true, - Optional: true, - Description: "do not set in config", - }, - "int": { - Type: schema.TypeInt, - Optional: true, - }, - }, - } -} - -func testResourceCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - - errMsg, _ := d.Get("apply_error").(string) - if errMsg != "" { - return errors.New(errMsg) - } - - // Required must make it through to Create - if _, ok := d.GetOk("required"); !ok { - return fmt.Errorf("Missing attribute 'required', but it's required!") - } - if _, ok := d.GetOk("required_map"); !ok { - return fmt.Errorf("Missing attribute 'required_map', but it's required!") - } - - d.Set("computed_from_required", d.Get("required")) - - return testResourceRead(d, meta) -} - -func testResourceRead(d *schema.ResourceData, meta interface{}) error { - d.Set("computed_read_only", "value_from_api") - d.Set("computed_read_only_force_new", "value_from_api") - if _, ok := d.GetOk("optional_computed_map"); !ok { - d.Set("optional_computed_map", map[string]string{}) - } - d.Set("computed_map", map[string]string{"key1": "value1"}) - d.Set("computed_list", []string{"listval1", "listval2"}) - d.Set("computed_set", []string{"setval1", "setval2"}) - - d.Set("planned_computed", d.Get("optional")) - - // if there is no "set" value, erroneously set it to an empty set. This - // might change a null value to an empty set, but we should be able to - // ignore that. - s := d.Get("set") - if s == nil || s.(*schema.Set).Len() == 0 { - d.Set("set", []interface{}{}) - } - - // This mimics many providers always setting a *string value. - // The existing behavior is that this will appear in the state as an empty - // string, which we have to maintain. - o := d.Get("optional") - if o == "" { - d.Set("optional", nil) - } - - // This should not show as set unless it's set in the config - _, ok := d.GetOkExists("get_ok_exists_false") - if ok { - return errors.New("get_ok_exists_false should not be set") - } - - return nil -} - -func testResourceUpdate(d *schema.ResourceData, meta interface{}) error { - errMsg, _ := d.Get("apply_error").(string) - if errMsg != "" { - return errors.New(errMsg) - } - return testResourceRead(d, meta) -} - -func testResourceDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_computed_set.go b/internal/legacy/builtin/providers/test/resource_computed_set.go deleted file mode 100644 index fa2035c8d..000000000 --- a/internal/legacy/builtin/providers/test/resource_computed_set.go +++ /dev/null @@ -1,123 +0,0 @@ -package test - -import ( - "bytes" - "fmt" - "math/rand" - "strings" - - "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceComputedSet() *schema.Resource { - return &schema.Resource{ - Create: testResourceComputedSetCreate, - Read: testResourceComputedSetRead, - Delete: testResourceComputedSetDelete, - Update: testResourceComputedSetUpdate, - - CustomizeDiff: func(d *schema.ResourceDiff, _ interface{}) error { - o, n := d.GetChange("set_count") - if o != n { - d.SetNewComputed("string_set") - } - return nil - }, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "set_count": { - Type: schema.TypeInt, - Optional: true, - }, - "string_set": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Set: schema.HashString, - }, - - "rule": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, - }, - - "ip_protocol": { - Type: schema.TypeString, - Required: true, - ForceNew: false, - }, - - "cidr": { - Type: schema.TypeString, - Optional: true, - ForceNew: false, - StateFunc: func(v interface{}) string { - return strings.ToLower(v.(string)) - }, - }, - }, - }, - }, - "optional_set": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - }, - } -} - -func computeSecGroupV2RuleHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string))) - buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["cidr"].(string)))) - - return hashcode.String(buf.String()) -} - -func testResourceComputedSetCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId(fmt.Sprintf("%x", rand.Int63())) - return testResourceComputedSetRead(d, meta) -} - -func testResourceComputedSetRead(d *schema.ResourceData, meta interface{}) error { - count := 3 - v, ok := d.GetOk("set_count") - if ok { - count = v.(int) - } - - var set []interface{} - for i := 0; i < count; i++ { - set = append(set, fmt.Sprintf("%d", i)) - } - - d.Set("string_set", schema.NewSet(schema.HashString, set)) - - // This isn't computed, but we should be able to ignore without issues. - d.Set("optional_set", []interface{}{}) - return nil -} - -func testResourceComputedSetUpdate(d *schema.ResourceData, meta interface{}) error { - return testResourceComputedSetRead(d, meta) -} - -func testResourceComputedSetDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_config_mode.go b/internal/legacy/builtin/providers/test/resource_config_mode.go deleted file mode 100644 index bb1da1550..000000000 --- a/internal/legacy/builtin/providers/test/resource_config_mode.go +++ /dev/null @@ -1,78 +0,0 @@ -package test - -import ( - "fmt" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceConfigMode() *schema.Resource { - return &schema.Resource{ - Create: testResourceConfigModeCreate, - Read: testResourceConfigModeRead, - Delete: testResourceConfigModeDelete, - Update: testResourceConfigModeUpdate, - - Schema: map[string]*schema.Schema{ - "resource_as_attr": { - Type: schema.TypeList, - ConfigMode: schema.SchemaConfigModeAttr, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "foo": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "nested_set": { - Type: schema.TypeSet, - Optional: true, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Type: schema.TypeString, - Optional: true, - }, - "set": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - }, - } -} - -func testResourceConfigModeCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("placeholder") - return testResourceConfigModeRead(d, meta) -} - -func testResourceConfigModeRead(d *schema.ResourceData, meta interface{}) error { - if l, ok := d.Get("resource_as_attr").([]interface{}); !ok { - return fmt.Errorf("resource_as_attr should appear as []interface{}, not %T", l) - } else { - for i, item := range l { - if _, ok := item.(map[string]interface{}); !ok { - return fmt.Errorf("resource_as_attr[%d] should appear as map[string]interface{}, not %T", i, item) - } - } - } - return nil -} - -func testResourceConfigModeUpdate(d *schema.ResourceData, meta interface{}) error { - return testResourceConfigModeRead(d, meta) -} - -func testResourceConfigModeDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_defaults.go b/internal/legacy/builtin/providers/test/resource_defaults.go deleted file mode 100644 index f46a4bb02..000000000 --- a/internal/legacy/builtin/providers/test/resource_defaults.go +++ /dev/null @@ -1,70 +0,0 @@ -package test - -import ( - "fmt" - "math/rand" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceDefaults() *schema.Resource { - return &schema.Resource{ - Create: testResourceDefaultsCreate, - Read: testResourceDefaultsRead, - Delete: testResourceDefaultsDelete, - Update: testResourceDefaultsUpdate, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "default_string": { - Type: schema.TypeString, - Optional: true, - Default: "default string", - }, - "default_bool": { - Type: schema.TypeString, - Optional: true, - Default: true, - }, - "nested": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "string": { - Type: schema.TypeString, - Optional: true, - Default: "default nested", - }, - "optional": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - }, - } -} - -func testResourceDefaultsCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId(fmt.Sprintf("%x", rand.Int63())) - return testResourceDefaultsRead(d, meta) -} - -func testResourceDefaultsUpdate(d *schema.ResourceData, meta interface{}) error { - return testResourceDefaultsRead(d, meta) -} - -func testResourceDefaultsRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceDefaultsDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_deprecated.go b/internal/legacy/builtin/providers/test/resource_deprecated.go deleted file mode 100644 index d78722208..000000000 --- a/internal/legacy/builtin/providers/test/resource_deprecated.go +++ /dev/null @@ -1,119 +0,0 @@ -package test - -import ( - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceDeprecated() *schema.Resource { - return &schema.Resource{ - Create: testResourceDeprecatedCreate, - Read: testResourceDeprecatedRead, - Update: testResourceDeprecatedUpdate, - Delete: testResourceDeprecatedDelete, - - Schema: map[string]*schema.Schema{ - "map_deprecated": { - Type: schema.TypeMap, - Optional: true, - Deprecated: "deprecated", - }, - "map_removed": { - Type: schema.TypeMap, - Optional: true, - Removed: "removed", - }, - "set_block_deprecated": { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Deprecated: "deprecated", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Type: schema.TypeString, - Required: true, - Deprecated: "deprecated", - }, - "optional": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - Deprecated: "deprecated", - }, - }, - }, - }, - "set_block_removed": { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Removed: "Removed", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "optional": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - Computed: true, - Removed: "removed", - }, - }, - }, - }, - "list_block_deprecated": { - Type: schema.TypeList, - Optional: true, - Deprecated: "deprecated", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Type: schema.TypeString, - Required: true, - Deprecated: "deprecated", - }, - "optional": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - Deprecated: "deprecated", - }, - }, - }, - }, - "list_block_removed": { - Type: schema.TypeList, - Optional: true, - Removed: "removed", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "optional": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - Removed: "removed", - }, - }, - }, - }, - }, - } -} - -func testResourceDeprecatedCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - return nil -} - -func testResourceDeprecatedRead(d *schema.ResourceData, meta interface{}) error { - - return nil -} - -func testResourceDeprecatedUpdate(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceDeprecatedDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_diff_suppress.go b/internal/legacy/builtin/providers/test/resource_diff_suppress.go deleted file mode 100644 index 309cc4488..000000000 --- a/internal/legacy/builtin/providers/test/resource_diff_suppress.go +++ /dev/null @@ -1,104 +0,0 @@ -package test - -import ( - "fmt" - "math/rand" - "strings" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceDiffSuppress() *schema.Resource { - diffSuppress := func(k, old, new string, d *schema.ResourceData) bool { - if old == "" || strings.Contains(new, "replace") { - return false - } - return true - } - - return &schema.Resource{ - Create: testResourceDiffSuppressCreate, - Read: testResourceDiffSuppressRead, - Delete: testResourceDiffSuppressDelete, - Update: testResourceDiffSuppressUpdate, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "optional": { - Type: schema.TypeString, - Optional: true, - }, - "val_to_upper": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - StateFunc: func(val interface{}) string { - return strings.ToUpper(val.(string)) - }, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.ToUpper(old) == strings.ToUpper(new) - }, - }, - "network": { - Type: schema.TypeString, - Optional: true, - Default: "default", - ForceNew: true, - DiffSuppressFunc: diffSuppress, - }, - "subnetwork": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - DiffSuppressFunc: diffSuppress, - }, - - "node_pool": { - Type: schema.TypeList, - Optional: true, - Computed: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - }, - }, - }, - }, - } -} - -func testResourceDiffSuppressCreate(d *schema.ResourceData, meta interface{}) error { - d.Set("network", "modified") - d.Set("subnetwork", "modified") - - if _, ok := d.GetOk("node_pool"); !ok { - d.Set("node_pool", []string{}) - } - - id := fmt.Sprintf("%x", rand.Int63()) - d.SetId(id) - return nil -} - -func testResourceDiffSuppressRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceDiffSuppressUpdate(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceDiffSuppressDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_force_new.go b/internal/legacy/builtin/providers/test/resource_force_new.go deleted file mode 100644 index 85721141e..000000000 --- a/internal/legacy/builtin/providers/test/resource_force_new.go +++ /dev/null @@ -1,39 +0,0 @@ -package test - -import ( - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceForceNew() *schema.Resource { - return &schema.Resource{ - Create: testResourceForceNewCreate, - Read: testResourceForceNewRead, - Delete: testResourceForceNewDelete, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "triggers": { - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - }, - }, - } -} - -func testResourceForceNewCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - return testResourceForceNewRead(d, meta) -} - -func testResourceForceNewRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceForceNewDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_gh12183.go b/internal/legacy/builtin/providers/test/resource_gh12183.go deleted file mode 100644 index f75fbe2e9..000000000 --- a/internal/legacy/builtin/providers/test/resource_gh12183.go +++ /dev/null @@ -1,64 +0,0 @@ -package test - -import ( - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -// This is a test resource to help reproduce GH-12183. This issue came up -// as a complex mixing of core + helper/schema and while we added core tests -// to cover some of the cases, this test helps top it off with an end-to-end -// test. -func testResourceGH12183() *schema.Resource { - return &schema.Resource{ - Create: testResourceCreate_gh12183, - Read: testResourceRead_gh12183, - Update: testResourceUpdate_gh12183, - Delete: testResourceDelete_gh12183, - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - - "config": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MinItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - }, - - "rules": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - }, - }, - }, - }, - } -} - -func testResourceCreate_gh12183(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - return testResourceRead_gh12183(d, meta) -} - -func testResourceRead_gh12183(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceUpdate_gh12183(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceDelete_gh12183(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_list.go b/internal/legacy/builtin/providers/test/resource_list.go deleted file mode 100644 index 9c69f915c..000000000 --- a/internal/legacy/builtin/providers/test/resource_list.go +++ /dev/null @@ -1,192 +0,0 @@ -package test - -import ( - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceList() *schema.Resource { - return &schema.Resource{ - Create: testResourceListCreate, - Read: testResourceListRead, - Update: testResourceListUpdate, - Delete: testResourceListDelete, - - CustomizeDiff: func(d *schema.ResourceDiff, _ interface{}) error { - if d.HasChange("dependent_list") { - d.SetNewComputed("computed_list") - } - return nil - }, - - Schema: map[string]*schema.Schema{ - "list_block": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "string": { - Type: schema.TypeString, - Optional: true, - }, - "int": { - Type: schema.TypeInt, - Optional: true, - }, - "force_new": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "sublist": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "sublist_block": { - Type: schema.TypeList, - Optional: true, - Computed: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "string": { - Type: schema.TypeString, - Required: true, - }, - "int": { - Type: schema.TypeInt, - Required: true, - }, - }, - }, - }, - "sublist_block_optional": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "list": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - }, - }, - }, - "dependent_list": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "val": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "computed_list": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "min_items": { - Type: schema.TypeList, - Optional: true, - MinItems: 2, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "val": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "never_set": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "sublist": { - Type: schema.TypeList, - MaxItems: 1, - ForceNew: true, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "bool": { - Type: schema.TypeBool, - ForceNew: true, - Required: true, - }, - "string": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - }, - }, - }, - "map_list": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeMap}, - }, - }, - } -} - -func testResourceListCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - return testResourceListRead(d, meta) -} - -func testResourceListRead(d *schema.ResourceData, meta interface{}) error { - fixedIps := d.Get("dependent_list") - - // all_fixed_ips should be set as computed with a CustomizeDiff func, but - // we're trying to emulate legacy provider behavior, and updating a - // computed field was a common case. - ips := []interface{}{} - if fixedIps != nil { - for _, v := range fixedIps.([]interface{}) { - m := v.(map[string]interface{}) - ips = append(ips, m["val"]) - } - } - if err := d.Set("computed_list", ips); err != nil { - return err - } - - // "computing" these values should insert empty containers into the - // never_set block. - values := make(map[string]interface{}) - values["sublist"] = []interface{}{} - d.Set("never_set", []interface{}{values}) - - return nil -} - -func testResourceListUpdate(d *schema.ResourceData, meta interface{}) error { - block := d.Get("never_set").([]interface{}) - if len(block) > 0 { - // if profiles contains any values, they should not be nil - _ = block[0].(map[string]interface{}) - } - return testResourceListRead(d, meta) -} - -func testResourceListDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_list_set.go b/internal/legacy/builtin/providers/test/resource_list_set.go deleted file mode 100644 index fe54d9235..000000000 --- a/internal/legacy/builtin/providers/test/resource_list_set.go +++ /dev/null @@ -1,192 +0,0 @@ -package test - -import ( - "fmt" - "math/rand" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceListSet() *schema.Resource { - return &schema.Resource{ - Create: testResourceListSetCreate, - Read: testResourceListSetRead, - Delete: testResourceListSetDelete, - Update: testResourceListSetUpdate, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "list": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "set": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "elem": { - Type: schema.TypeString, - Optional: true, - DiffSuppressFunc: func(_, o, n string, _ *schema.ResourceData) bool { - return o == n - }, - }, - }, - }, - Set: func(v interface{}) int { - raw := v.(map[string]interface{}) - if el, ok := raw["elem"]; ok { - return schema.HashString(el) - } - return 42 - }, - }, - }, - }, - }, - "replication_configuration": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "role": { - Type: schema.TypeString, - Required: true, - }, - "rules": { - Type: schema.TypeSet, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Optional: true, - }, - "destination": { - Type: schema.TypeSet, - MaxItems: 1, - MinItems: 1, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "account_id": { - Type: schema.TypeString, - Optional: true, - }, - "bucket": { - Type: schema.TypeString, - Required: true, - }, - "storage_class": { - Type: schema.TypeString, - Optional: true, - }, - "replica_kms_key_id": { - Type: schema.TypeString, - Optional: true, - }, - "access_control_translation": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "owner": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - }, - }, - }, - "source_selection_criteria": { - Type: schema.TypeSet, - Optional: true, - MinItems: 1, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "sse_kms_encrypted_objects": { - Type: schema.TypeSet, - Optional: true, - MinItems: 1, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled": { - Type: schema.TypeBool, - Required: true, - }, - }, - }, - }, - }, - }, - }, - "prefix": { - Type: schema.TypeString, - Optional: true, - }, - "status": { - Type: schema.TypeString, - Required: true, - }, - "priority": { - Type: schema.TypeInt, - Optional: true, - }, - "filter": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "prefix": { - Type: schema.TypeString, - Optional: true, - }, - "tags": { - Type: schema.TypeMap, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func testResourceListSetCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId(fmt.Sprintf("%x", rand.Int63())) - return testResourceListSetRead(d, meta) -} - -func testResourceListSetUpdate(d *schema.ResourceData, meta interface{}) error { - return testResourceListSetRead(d, meta) -} - -func testResourceListSetRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceListSetDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_map.go b/internal/legacy/builtin/providers/test/resource_map.go deleted file mode 100644 index fe1ece1e9..000000000 --- a/internal/legacy/builtin/providers/test/resource_map.go +++ /dev/null @@ -1,77 +0,0 @@ -package test - -import ( - "fmt" - - "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceMap() *schema.Resource { - return &schema.Resource{ - Create: testResourceMapCreate, - Read: testResourceMapRead, - Update: testResourceMapUpdate, - Delete: testResourceMapDelete, - - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - }, - "map_of_three": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - ValidateFunc: func(v interface{}, _ string) ([]string, []error) { - errs := []error{} - for k, v := range v.(map[string]interface{}) { - if v == hcl2shim.UnknownVariableValue { - errs = append(errs, fmt.Errorf("unknown value in ValidateFunc: %q=%q", k, v)) - } - } - return nil, errs - }, - }, - "map_values": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "computed_map": { - Type: schema.TypeMap, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - } -} - -func testResourceMapCreate(d *schema.ResourceData, meta interface{}) error { - // make sure all elements are passed to the map - m := d.Get("map_of_three").(map[string]interface{}) - if len(m) != 3 { - return fmt.Errorf("expected 3 map values, got %#v\n", m) - } - - d.SetId("testId") - return testResourceMapRead(d, meta) -} - -func testResourceMapRead(d *schema.ResourceData, meta interface{}) error { - var computedMap map[string]interface{} - if v, ok := d.GetOk("map_values"); ok { - computedMap = v.(map[string]interface{}) - } - d.Set("computed_map", computedMap) - return nil -} - -func testResourceMapUpdate(d *schema.ResourceData, meta interface{}) error { - return testResourceMapRead(d, meta) -} - -func testResourceMapDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_nested.go b/internal/legacy/builtin/providers/test/resource_nested.go deleted file mode 100644 index c78274d53..000000000 --- a/internal/legacy/builtin/providers/test/resource_nested.go +++ /dev/null @@ -1,114 +0,0 @@ -package test - -import ( - "fmt" - "math/rand" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceNested() *schema.Resource { - return &schema.Resource{ - Create: testResourceNestedCreate, - Read: testResourceNestedRead, - Delete: testResourceNestedDelete, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "optional": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - }, - "nested": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "string": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "optional": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - }, - "nested_again": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "string": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - }, - }, - }, - }, - }, - }, - "list_block": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "sub_list_block": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "bool": { - Type: schema.TypeBool, - Optional: true, - }, - "set": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func testResourceNestedCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId(fmt.Sprintf("%x", rand.Int63())) - return testResourceNestedRead(d, meta) -} - -func testResourceNestedUpdate(d *schema.ResourceData, meta interface{}) error { - return testResourceNestedRead(d, meta) -} - -func testResourceNestedRead(d *schema.ResourceData, meta interface{}) error { - set := []map[string]interface{}{map[string]interface{}{ - "sub_list_block": []map[string]interface{}{map[string]interface{}{ - "bool": false, - "set": schema.NewSet(schema.HashString, nil), - }}, - }} - d.Set("list_block", set) - return nil -} - -func testResourceNestedDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_nested_id.go b/internal/legacy/builtin/providers/test/resource_nested_id.go deleted file mode 100644 index f5a2d9bf9..000000000 --- a/internal/legacy/builtin/providers/test/resource_nested_id.go +++ /dev/null @@ -1,48 +0,0 @@ -package test - -import ( - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceNestedId() *schema.Resource { - return &schema.Resource{ - Create: testResourceNestedIdCreate, - Read: testResourceNestedIdRead, - Update: testResourceNestedIdUpdate, - Delete: testResourceNestedIdDelete, - - Schema: map[string]*schema.Schema{ - "list_block": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - }, - }, - }, - }, - } -} - -func testResourceNestedIdCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - return nil -} - -func testResourceNestedIdRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceNestedIdUpdate(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceNestedIdDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_nested_set.go b/internal/legacy/builtin/providers/test/resource_nested_set.go deleted file mode 100644 index 4a1253400..000000000 --- a/internal/legacy/builtin/providers/test/resource_nested_set.go +++ /dev/null @@ -1,171 +0,0 @@ -package test - -import ( - "fmt" - "math/rand" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceNestedSet() *schema.Resource { - return &schema.Resource{ - Create: testResourceNestedSetCreate, - Read: testResourceNestedSetRead, - Delete: testResourceNestedSetDelete, - Update: testResourceNestedSetUpdate, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "optional": { - Type: schema.TypeBool, - Optional: true, - }, - "force_new": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "type_list": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - }, - }, - }, - }, - "single": { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - }, - - "optional": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - Computed: true, - }, - }, - }, - }, - "multi": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "set": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "required": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "optional_int": { - Type: schema.TypeInt, - Optional: true, - }, - "bool": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - - "optional": { - Type: schema.TypeString, - // commenting this causes it to get missed during apply - //ForceNew: true, - Optional: true, - }, - "bool": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - "with_list": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "required": { - Type: schema.TypeString, - Required: true, - }, - "list": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "list_block": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "unused": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func testResourceNestedSetCreate(d *schema.ResourceData, meta interface{}) error { - id := fmt.Sprintf("%x", rand.Int63()) - d.SetId(id) - - // replicate some awkward handling of a computed value in a set - set := d.Get("single").(*schema.Set) - l := set.List() - if len(l) == 1 { - if s, ok := l[0].(map[string]interface{}); ok { - if v, _ := s["optional"].(string); v == "" { - s["optional"] = id - } - } - } - - d.Set("single", set) - - return testResourceNestedSetRead(d, meta) -} - -func testResourceNestedSetRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceNestedSetDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} - -func testResourceNestedSetUpdate(d *schema.ResourceData, meta interface{}) error { - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_provider_meta.go b/internal/legacy/builtin/providers/test/resource_provider_meta.go deleted file mode 100644 index c900c5159..000000000 --- a/internal/legacy/builtin/providers/test/resource_provider_meta.go +++ /dev/null @@ -1,95 +0,0 @@ -package test - -import ( - "fmt" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceProviderMeta() *schema.Resource { - return &schema.Resource{ - Create: testResourceProviderMetaCreate, - Read: testResourceProviderMetaRead, - Update: testResourceProviderMetaUpdate, - Delete: testResourceProviderMetaDelete, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "optional": { - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -type providerMeta struct { - Foo string `cty:"foo"` -} - -func testResourceProviderMetaCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - var m providerMeta - - err := d.GetProviderMeta(&m) - if err != nil { - return err - } - - if m.Foo != "bar" { - return fmt.Errorf("expected provider_meta.foo to be %q, was %q", - "bar", m.Foo) - } - - return testResourceProviderMetaRead(d, meta) -} - -func testResourceProviderMetaRead(d *schema.ResourceData, meta interface{}) error { - var m providerMeta - - err := d.GetProviderMeta(&m) - if err != nil { - return err - } - - if m.Foo != "bar" { - return fmt.Errorf("expected provider_meta.foo to be %q, was %q", - "bar", m.Foo) - } - - return nil -} - -func testResourceProviderMetaUpdate(d *schema.ResourceData, meta interface{}) error { - var m providerMeta - - err := d.GetProviderMeta(&m) - if err != nil { - return err - } - - if m.Foo != "bar" { - return fmt.Errorf("expected provider_meta.foo to be %q, was %q", - "bar", m.Foo) - } - return testResourceProviderMetaRead(d, meta) -} - -func testResourceProviderMetaDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - var m providerMeta - - err := d.GetProviderMeta(&m) - if err != nil { - return err - } - - if m.Foo != "bar" { - return fmt.Errorf("expected provider_meta.foo to be %q, was %q", - "bar", m.Foo) - } - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_required_min.go b/internal/legacy/builtin/providers/test/resource_required_min.go deleted file mode 100644 index 23419ed02..000000000 --- a/internal/legacy/builtin/providers/test/resource_required_min.go +++ /dev/null @@ -1,68 +0,0 @@ -package test - -import ( - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceRequiredMin() *schema.Resource { - return &schema.Resource{ - Create: testResourceRequiredMinCreate, - Read: testResourceRequiredMinRead, - Update: testResourceRequiredMinUpdate, - Delete: testResourceRequiredMinDelete, - - CustomizeDiff: func(d *schema.ResourceDiff, _ interface{}) error { - if d.HasChange("dependent_list") { - d.SetNewComputed("computed_list") - } - return nil - }, - - Schema: map[string]*schema.Schema{ - "min_items": { - Type: schema.TypeList, - Optional: true, - MinItems: 2, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "val": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "required_min_items": { - Type: schema.TypeList, - Required: true, - MinItems: 2, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "val": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - }, - } -} - -func testResourceRequiredMinCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - return testResourceRequiredMinRead(d, meta) -} - -func testResourceRequiredMinRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceRequiredMinUpdate(d *schema.ResourceData, meta interface{}) error { - return testResourceRequiredMinRead(d, meta) -} - -func testResourceRequiredMinDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_signal.go b/internal/legacy/builtin/providers/test/resource_signal.go deleted file mode 100644 index 398a996cd..000000000 --- a/internal/legacy/builtin/providers/test/resource_signal.go +++ /dev/null @@ -1,43 +0,0 @@ -package test - -import ( - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceSignal() *schema.Resource { - return &schema.Resource{ - Create: testResourceSignalCreate, - Read: testResourceSignalRead, - Update: testResourceSignalUpdate, - Delete: testResourceSignalDelete, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "optional": { - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -func testResourceSignalCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - - return testResourceSignalRead(d, meta) -} - -func testResourceSignalRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceSignalUpdate(d *schema.ResourceData, meta interface{}) error { - return testResourceSignalRead(d, meta) -} - -func testResourceSignalDelete(d *schema.ResourceData, meta interface{}) error { - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_state_func.go b/internal/legacy/builtin/providers/test/resource_state_func.go deleted file mode 100644 index 9589f102e..000000000 --- a/internal/legacy/builtin/providers/test/resource_state_func.go +++ /dev/null @@ -1,118 +0,0 @@ -package test - -import ( - "crypto/sha1" - "encoding/hex" - "fmt" - "math/rand" - - "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceStateFunc() *schema.Resource { - return &schema.Resource{ - Create: testResourceStateFuncCreate, - Read: testResourceStateFuncRead, - Update: testResourceStateFuncUpdate, - Delete: testResourceStateFuncDelete, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "optional": { - Type: schema.TypeString, - Optional: true, - }, - "state_func": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - StateFunc: stateFuncHash, - }, - "state_func_value": { - Type: schema.TypeString, - Optional: true, - }, - - // set block with computed elements - "set_block": { - Type: schema.TypeSet, - Optional: true, - Set: setBlockHash, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "required": { - Type: schema.TypeString, - Required: true, - }, - "optional": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - }, - }, - }, - }, - } -} - -func stateFuncHash(v interface{}) string { - hash := sha1.Sum([]byte(v.(string))) - return hex.EncodeToString(hash[:]) -} - -func setBlockHash(v interface{}) int { - m := v.(map[string]interface{}) - required, _ := m["required"].(string) - optional, _ := m["optional"].(string) - return hashcode.String(fmt.Sprintf("%s|%s", required, optional)) -} - -func testResourceStateFuncCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId(fmt.Sprintf("%x", rand.Int63())) - - // if we have a reference for the actual data in the state_func field, - // compare it - if data, ok := d.GetOk("state_func_value"); ok { - expected := data.(string) - got := d.Get("state_func").(string) - if expected != got { - return fmt.Errorf("expected state_func value:%q, got%q", expected, got) - } - } - - // Check that we can lookup set elements by our computed hash. - // This is not advised, but we can use this to make sure the final diff was - // prepared with the correct values. - setBlock, ok := d.GetOk("set_block") - if ok { - set := setBlock.(*schema.Set) - for _, obj := range set.List() { - idx := setBlockHash(obj) - requiredAddr := fmt.Sprintf("%s.%d.%s", "set_block", idx, "required") - _, ok := d.GetOkExists(requiredAddr) - if !ok { - return fmt.Errorf("failed to get attr %q from %#v", fmt.Sprintf(requiredAddr), d.State().Attributes) - } - } - } - - return testResourceStateFuncRead(d, meta) -} - -func testResourceStateFuncRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceStateFuncUpdate(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceStateFuncDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_timeout.go b/internal/legacy/builtin/providers/test/resource_timeout.go deleted file mode 100644 index 17a6da0eb..000000000 --- a/internal/legacy/builtin/providers/test/resource_timeout.go +++ /dev/null @@ -1,125 +0,0 @@ -package test - -import ( - "fmt" - "time" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceTimeout() *schema.Resource { - return &schema.Resource{ - Create: testResourceTimeoutCreate, - Read: testResourceTimeoutRead, - Update: testResourceTimeoutUpdate, - Delete: testResourceTimeoutDelete, - - // Due to the schema version also being stashed in the private/meta - // data, we need to ensure that it does not overwrite the map - // containing the timeouts. - SchemaVersion: 1, - - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(time.Second), - Update: schema.DefaultTimeout(time.Second), - Delete: schema.DefaultTimeout(time.Second), - }, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "create_delay": { - Type: schema.TypeString, - Optional: true, - }, - "read_delay": { - Type: schema.TypeString, - Optional: true, - }, - "update_delay": { - Type: schema.TypeString, - Optional: true, - }, - "delete_delay": { - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -func testResourceTimeoutCreate(d *schema.ResourceData, meta interface{}) error { - delayString := d.Get("create_delay").(string) - var delay time.Duration - var err error - if delayString != "" { - delay, err = time.ParseDuration(delayString) - if err != nil { - return err - } - } - - if delay > d.Timeout(schema.TimeoutCreate) { - return fmt.Errorf("timeout while creating resource") - } - - d.SetId("testId") - - return testResourceRead(d, meta) -} - -func testResourceTimeoutRead(d *schema.ResourceData, meta interface{}) error { - delayString := d.Get("read_delay").(string) - var delay time.Duration - var err error - if delayString != "" { - delay, err = time.ParseDuration(delayString) - if err != nil { - return err - } - } - - if delay > d.Timeout(schema.TimeoutRead) { - return fmt.Errorf("timeout while reading resource") - } - - return nil -} - -func testResourceTimeoutUpdate(d *schema.ResourceData, meta interface{}) error { - delayString := d.Get("update_delay").(string) - var delay time.Duration - var err error - if delayString != "" { - delay, err = time.ParseDuration(delayString) - if err != nil { - return err - } - } - - if delay > d.Timeout(schema.TimeoutUpdate) { - return fmt.Errorf("timeout while updating resource") - } - return nil -} - -func testResourceTimeoutDelete(d *schema.ResourceData, meta interface{}) error { - delayString := d.Get("delete_delay").(string) - var delay time.Duration - var err error - if delayString != "" { - delay, err = time.ParseDuration(delayString) - if err != nil { - return err - } - } - - if delay > d.Timeout(schema.TimeoutDelete) { - return fmt.Errorf("timeout while deleting resource") - } - - d.SetId("") - return nil -} diff --git a/internal/legacy/builtin/providers/test/resource_undeletable.go b/internal/legacy/builtin/providers/test/resource_undeletable.go deleted file mode 100644 index b4be0bff3..000000000 --- a/internal/legacy/builtin/providers/test/resource_undeletable.go +++ /dev/null @@ -1,30 +0,0 @@ -package test - -import ( - "fmt" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceUndeleteable() *schema.Resource { - return &schema.Resource{ - Create: testResourceUndeleteableCreate, - Read: testResourceUndeleteableRead, - Delete: testResourceUndeleteableDelete, - - Schema: map[string]*schema.Schema{}, - } -} - -func testResourceUndeleteableCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("placeholder") - return testResourceUndeleteableRead(d, meta) -} - -func testResourceUndeleteableRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func testResourceUndeleteableDelete(d *schema.ResourceData, meta interface{}) error { - return fmt.Errorf("test_undeleteable always fails deletion (use terraform state rm if you really want to delete it)") -} diff --git a/internal/legacy/builtin/providers/test/resource_with_custom_diff.go b/internal/legacy/builtin/providers/test/resource_with_custom_diff.go deleted file mode 100644 index 397e0795e..000000000 --- a/internal/legacy/builtin/providers/test/resource_with_custom_diff.go +++ /dev/null @@ -1,154 +0,0 @@ -package test - -import ( - "fmt" - - "github.com/hashicorp/terraform/internal/legacy/helper/schema" -) - -func testResourceCustomDiff() *schema.Resource { - return &schema.Resource{ - Create: testResourceCustomDiffCreate, - Read: testResourceCustomDiffRead, - CustomizeDiff: testResourceCustomDiffCustomizeDiff, - Update: testResourceCustomDiffUpdate, - Delete: testResourceCustomDiffDelete, - Schema: map[string]*schema.Schema{ - "required": { - Type: schema.TypeString, - Required: true, - }, - "computed": { - Type: schema.TypeInt, - Computed: true, - }, - "index": { - Type: schema.TypeInt, - Computed: true, - }, - "veto": { - Type: schema.TypeBool, - Optional: true, - }, - "list": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - } -} - -type listDiffCases struct { - Type string - Value string -} - -func testListDiffCases(index int) []listDiffCases { - switch index { - case 0: - return []listDiffCases{ - { - Type: "add", - Value: "dc1", - }, - } - case 1: - return []listDiffCases{ - { - Type: "remove", - Value: "dc1", - }, - { - Type: "add", - Value: "dc2", - }, - { - Type: "add", - Value: "dc3", - }, - } - } - return nil -} - -func testListDiffCasesReadResult(index int) []interface{} { - switch index { - case 1: - return []interface{}{"dc1"} - default: - return []interface{}{"dc2", "dc3"} - } -} - -func testResourceCustomDiffCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId("testId") - - // Required must make it through to Create - if _, ok := d.GetOk("required"); !ok { - return fmt.Errorf("missing attribute 'required', but it's required") - } - - _, new := d.GetChange("computed") - expected := new.(int) - 1 - actual := d.Get("index").(int) - if expected != actual { - return fmt.Errorf("expected computed to be 1 ahead of index, got computed: %d, index: %d", expected, actual) - } - d.Set("index", new) - - return testResourceCustomDiffRead(d, meta) -} - -func testResourceCustomDiffRead(d *schema.ResourceData, meta interface{}) error { - if err := d.Set("list", testListDiffCasesReadResult(d.Get("index").(int))); err != nil { - return err - } - return nil -} - -func testResourceCustomDiffCustomizeDiff(d *schema.ResourceDiff, meta interface{}) error { - if d.Get("veto").(bool) == true { - return fmt.Errorf("veto is true, diff vetoed") - } - // Note that this gets put into state after the update, regardless of whether - // or not anything is acted upon in the diff. - d.SetNew("computed", d.Get("computed").(int)+1) - - // This tests a diffed list, based off of the value of index - dcs := testListDiffCases(d.Get("index").(int)) - s := d.Get("list").([]interface{}) - for _, dc := range dcs { - switch dc.Type { - case "add": - s = append(s, dc.Value) - case "remove": - for i := range s { - if s[i].(string) == dc.Value { - copy(s[i:], s[i+1:]) - s = s[:len(s)-1] - break - } - } - } - } - d.SetNew("list", s) - - return nil -} - -func testResourceCustomDiffUpdate(d *schema.ResourceData, meta interface{}) error { - _, new := d.GetChange("computed") - expected := new.(int) - 1 - actual := d.Get("index").(int) - if expected != actual { - return fmt.Errorf("expected computed to be 1 ahead of index, got computed: %d, index: %d", expected, actual) - } - d.Set("index", new) - return testResourceCustomDiffRead(d, meta) -} - -func testResourceCustomDiffDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} diff --git a/internal/provider-simple/main/main.go b/internal/provider-simple/main/main.go new file mode 100644 index 000000000..be0ad2ef4 --- /dev/null +++ b/internal/provider-simple/main/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/hashicorp/terraform/internal/grpcwrap" + simple "github.com/hashicorp/terraform/internal/provider-simple" + "github.com/hashicorp/terraform/internal/tfplugin5" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + GRPCProviderFunc: func() tfplugin5.ProviderServer { + return grpcwrap.Provider(simple.Provider()) + }, + }) +} diff --git a/internal/provider-simple/provider.go b/internal/provider-simple/provider.go new file mode 100644 index 000000000..2da9ac6c0 --- /dev/null +++ b/internal/provider-simple/provider.go @@ -0,0 +1,128 @@ +// simple provider a minimal provider implementation for testing +package simple + +import ( + "errors" + "time" + + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/providers" + "github.com/zclconf/go-cty/cty" + ctyjson "github.com/zclconf/go-cty/cty/json" +) + +type simple struct { + schema providers.GetSchemaResponse +} + +func Provider() providers.Interface { + simpleResource := providers.Schema{ + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Computed: true, + Type: cty.String, + }, + "value": { + Optional: true, + Type: cty.String, + }, + }, + }, + } + + return simple{ + schema: providers.GetSchemaResponse{ + Provider: providers.Schema{ + Block: nil, + }, + ResourceTypes: map[string]providers.Schema{ + "simple_resource": simpleResource, + }, + DataSources: map[string]providers.Schema{ + "simple_resource": simpleResource, + }, + }, + } +} + +func (s simple) GetSchema() providers.GetSchemaResponse { + return s.schema +} + +func (s simple) PrepareProviderConfig(req providers.PrepareProviderConfigRequest) (resp providers.PrepareProviderConfigResponse) { + return resp +} + +func (s simple) ValidateResourceTypeConfig(req providers.ValidateResourceTypeConfigRequest) (resp providers.ValidateResourceTypeConfigResponse) { + return resp +} + +func (s simple) ValidateDataSourceConfig(req providers.ValidateDataSourceConfigRequest) (resp providers.ValidateDataSourceConfigResponse) { + return resp +} + +func (p simple) UpgradeResourceState(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) { + ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType() + val, err := ctyjson.Unmarshal(req.RawStateJSON, ty) + resp.Diagnostics = resp.Diagnostics.Append(err) + resp.UpgradedState = val + return resp +} + +func (s simple) Configure(providers.ConfigureRequest) (resp providers.ConfigureResponse) { + return resp +} + +func (s simple) Stop() error { + return nil +} + +func (s simple) ReadResource(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { + // just return the same state we received + resp.NewState = req.PriorState + return resp +} + +func (s simple) PlanResourceChange(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { + m := req.ProposedNewState.AsValueMap() + _, ok := m["id"] + if !ok { + m["id"] = cty.UnknownVal(cty.String) + } + + resp.PlannedState = cty.ObjectVal(m) + return resp +} + +func (s simple) ApplyResourceChange(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { + if req.PlannedState.IsNull() { + resp.NewState = req.PlannedState + return resp + } + + m := req.PlannedState.AsValueMap() + _, ok := m["id"] + if !ok { + m["id"] = cty.StringVal(time.Now().String()) + } + resp.NewState = cty.ObjectVal(m) + + return resp +} + +func (s simple) ImportResourceState(providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) { + resp.Diagnostics = resp.Diagnostics.Append(errors.New("unsupported")) + return resp +} + +func (s simple) ReadDataSource(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { + m := req.Config.AsValueMap() + m["id"] = cty.StringVal("static_id") + resp.State = cty.ObjectVal(m) + return resp +} + +func (s simple) Close() error { + return nil +} diff --git a/internal/provider-terraform/main/main.go b/internal/provider-terraform/main/main.go new file mode 100644 index 000000000..a8ad4bd49 --- /dev/null +++ b/internal/provider-terraform/main/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/terraform" + "github.com/hashicorp/terraform/internal/grpcwrap" + "github.com/hashicorp/terraform/internal/tfplugin5" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + // Provide a binary version of the internal terraform provider for testing + plugin.Serve(&plugin.ServeOpts{ + GRPCProviderFunc: func() tfplugin5.ProviderServer { + return grpcwrap.Provider(terraform.NewProvider()) + }, + }) +} diff --git a/internal/provisioner-local-exec/main/main.go b/internal/provisioner-local-exec/main/main.go new file mode 100644 index 000000000..86a6f07fc --- /dev/null +++ b/internal/provisioner-local-exec/main/main.go @@ -0,0 +1,17 @@ +package main + +import ( + localexec "github.com/hashicorp/terraform/builtin/provisioners/local-exec" + "github.com/hashicorp/terraform/internal/grpcwrap" + "github.com/hashicorp/terraform/internal/tfplugin5" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + // Provide a binary version of the internal terraform provider for testing + plugin.Serve(&plugin.ServeOpts{ + GRPCProvisionerFunc: func() tfplugin5.ProvisionerServer { + return grpcwrap.Provisioner(localexec.New()) + }, + }) +} diff --git a/scripts/generate-plugins.go b/scripts/generate-plugins.go deleted file mode 100644 index 24030bcf6..000000000 --- a/scripts/generate-plugins.go +++ /dev/null @@ -1,285 +0,0 @@ -// Generate Plugins is a small program that updates the lists of plugins in -// command/internal_plugin_list.go so they will be compiled into the main -// terraform binary. -package main - -import ( - "fmt" - "go/ast" - "go/parser" - "go/token" - "io/ioutil" - "log" - "os" - "path/filepath" - "sort" - "strings" -) - -const target = "command/internal_plugin_list.go" - -func main() { - if isProjectRoot() == false { - log.Fatalf("This program must be invoked in the terraform project root") - } - - //// Collect all of the data we need about plugins we have in the project - //providers, err := discoverProviders() - //if err != nil { - // log.Fatalf("Failed to discover providers: %s", err) - //} - - provisioners, err := discoverProvisioners() - if err != nil { - log.Fatalf("Failed to discover provisioners: %s", err) - } - - // Do some simple code generation and templating - output := source - output = strings.Replace(output, "IMPORTS", makeImports(nil, provisioners), 1) - //output = strings.Replace(output, "PROVIDERS", makeProviderMap(providers), 1) - output = strings.Replace(output, "PROVISIONERS", makeProvisionerMap(provisioners), 1) - - // TODO sort the lists of plugins so we are not subjected to random OS ordering of the plugin lists - - // Write our generated code to the command/plugin.go file - file, err := os.Create(target) - defer file.Close() - if err != nil { - log.Fatalf("Failed to open %s for writing: %s", target, err) - } - - _, err = file.WriteString(output) - if err != nil { - log.Fatalf("Failed writing to %s: %s", target, err) - } - - log.Printf("Generated %s", target) -} - -type plugin struct { - Package string // Package name from ast remoteexec - PluginName string // Path via deriveName() remote-exec - TypeName string // Type of plugin provisioner - Path string // Relative import path builtin/provisioners/remote-exec - ImportName string // See deriveImport() remoteexecprovisioner -} - -// makeProviderMap creates a map of providers like this: -// -// var InternalProviders = map[string]plugin.ProviderFunc{ -// "aws": aws.Provider, -// "azurerm": azurerm.Provider, -// "cloudflare": cloudflare.Provider, -func makeProviderMap(items []plugin) string { - output := "" - for _, item := range items { - output += fmt.Sprintf("\t\"%s\": %s.%s,\n", item.PluginName, item.ImportName, item.TypeName) - } - return output -} - -func isProjectRoot() bool { - _, err := os.Stat("go.mod") - if os.IsNotExist(err) { - return false - } - - return true -} - -// makeProvisionerMap creates a map of provisioners like this: -// -// "chef": chefprovisioner.Provisioner, -// "salt-masterless": saltmasterlessprovisioner.Provisioner, -// "file": fileprovisioner.Provisioner, -// "local-exec": localexecprovisioner.Provisioner, -// "remote-exec": remoteexecprovisioner.Provisioner, -// -func makeProvisionerMap(items []plugin) string { - output := "" - for _, item := range items { - output += fmt.Sprintf("\t\"%s\": %s.%s,\n", item.PluginName, item.ImportName, item.TypeName) - } - return output -} - -func makeImports(providers, provisioners []plugin) string { - plugins := []string{} - - for _, provider := range providers { - plugins = append(plugins, fmt.Sprintf("\t%s \"github.com/hashicorp/terraform/%s\"\n", provider.ImportName, filepath.ToSlash(provider.Path))) - } - - for _, provisioner := range provisioners { - plugins = append(plugins, fmt.Sprintf("\t%s \"github.com/hashicorp/terraform/%s\"\n", provisioner.ImportName, filepath.ToSlash(provisioner.Path))) - } - - // Make things pretty - sort.Strings(plugins) - - return strings.Join(plugins, "") -} - -// listDirectories recursively lists directories under the specified path -func listDirectories(path string) ([]string, error) { - names := []string{} - items, err := ioutil.ReadDir(path) - if err != nil { - return names, err - } - - for _, item := range items { - // We only want directories - if item.IsDir() { - if item.Name() == "testdata" { - continue - } - currentDir := filepath.Join(path, item.Name()) - names = append(names, currentDir) - - // Do some recursion - subNames, err := listDirectories(currentDir) - if err == nil { - names = append(names, subNames...) - } - } - } - - return names, nil -} - -// deriveName determines the name of the plugin relative to the specified root -// path. -func deriveName(root, full string) string { - short, _ := filepath.Rel(root, full) - bits := strings.Split(short, string(os.PathSeparator)) - return strings.Join(bits, "-") -} - -// deriveImport will build a unique import identifier based on packageName and -// the result of deriveName(). This is important for disambigutating between -// providers and provisioners that have the same name. This will be something -// like: -// -// remote-exec -> remoteexecprovisioner -// -// which is long, but is deterministic and unique. -func deriveImport(typeName, derivedName string) string { - return strings.Replace(derivedName, "-", "", -1) + strings.ToLower(typeName) -} - -// discoverTypesInPath searches for types of typeID in path using go's ast and -// returns a list of plugins it finds. -func discoverTypesInPath(path, typeID, typeName string) ([]plugin, error) { - pluginTypes := []plugin{} - - dirs, err := listDirectories(path) - if err != nil { - return pluginTypes, err - } - - for _, dir := range dirs { - fset := token.NewFileSet() - goPackages, err := parser.ParseDir(fset, dir, nil, parser.AllErrors) - if err != nil { - return pluginTypes, fmt.Errorf("Failed parsing directory %s: %s", dir, err) - } - - for _, goPackage := range goPackages { - ast.PackageExports(goPackage) - ast.Inspect(goPackage, func(n ast.Node) bool { - switch x := n.(type) { - case *ast.FuncDecl: - // If we get a function then we will check the function name - // against typeName and the function return type (Results) - // against typeID. - // - // There may be more than one return type but in the target - // case there should only be one. Also the return type is a - // ast.SelectorExpr which means we have multiple nodes. - // We'll read all of them as ast.Ident (identifier), join - // them via . to get a string like terraform.ResourceProvider - // and see if it matches our expected typeID - // - // This is somewhat verbose but prevents us from identifying - // the wrong types if the function name is amiguous or if - // there are other subfolders added later. - if x.Name.Name == typeName && len(x.Type.Results.List) == 1 { - node := x.Type.Results.List[0].Type - typeIdentifiers := []string{} - ast.Inspect(node, func(m ast.Node) bool { - switch y := m.(type) { - case *ast.Ident: - typeIdentifiers = append(typeIdentifiers, y.Name) - } - // We need all of the identifiers to join so we - // can't break early here. - return true - }) - if strings.Join(typeIdentifiers, ".") == typeID { - derivedName := deriveName(path, dir) - pluginTypes = append(pluginTypes, plugin{ - Package: goPackage.Name, - PluginName: derivedName, - ImportName: deriveImport(x.Name.Name, derivedName), - TypeName: x.Name.Name, - Path: dir, - }) - } - } - case *ast.TypeSpec: - // In the simpler case we will simply check whether the type - // declaration has the name we were looking for. - if x.Name.Name == typeID { - derivedName := deriveName(path, dir) - pluginTypes = append(pluginTypes, plugin{ - Package: goPackage.Name, - PluginName: derivedName, - ImportName: deriveImport(x.Name.Name, derivedName), - TypeName: x.Name.Name, - Path: dir, - }) - // The AST stops parsing when we return false. Once we - // find the symbol we want we can stop parsing. - return false - } - } - return true - }) - } - } - - return pluginTypes, nil -} - -func discoverProviders() ([]plugin, error) { - path := "./builtin/providers" - typeID := "terraform.ResourceProvider" - typeName := "Provider" - return discoverTypesInPath(path, typeID, typeName) -} - -func discoverProvisioners() ([]plugin, error) { - path := "./builtin/provisioners" - typeID := "terraform.ResourceProvisioner" - typeName := "Provisioner" - return discoverTypesInPath(path, typeID, typeName) -} - -const source = `// -// This file is automatically generated by scripts/generate-plugins.go -- Do not edit! -// -package command - -import ( -IMPORTS - "github.com/hashicorp/terraform/plugin" -) - -var InternalProviders = map[string]plugin.ProviderFunc{} - -var InternalProvisioners = map[string]plugin.ProvisionerFunc{ -PROVISIONERS -} -` diff --git a/scripts/generate-plugins_test.go b/scripts/generate-plugins_test.go deleted file mode 100644 index cba015b85..000000000 --- a/scripts/generate-plugins_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package main - -import "testing" - -func TestMakeProvisionerMap(t *testing.T) { - p := makeProvisionerMap([]plugin{ - { - Package: "file", - PluginName: "file", - TypeName: "Provisioner", - Path: "builtin/provisioners/file", - ImportName: "fileprovisioner", - }, - { - Package: "localexec", - PluginName: "local-exec", - TypeName: "Provisioner", - Path: "builtin/provisioners/local-exec", - ImportName: "localexecprovisioner", - }, - { - Package: "remoteexec", - PluginName: "remote-exec", - TypeName: "Provisioner", - Path: "builtin/provisioners/remote-exec", - ImportName: "remoteexecprovisioner", - }, - }) - - expected := ` "file": fileprovisioner.Provisioner, - "local-exec": localexecprovisioner.Provisioner, - "remote-exec": remoteexecprovisioner.Provisioner, -` - - if p != expected { - t.Errorf("Provisioner output does not match expected format.\n -- Expected -- \n%s\n -- Found --\n%s\n", expected, p) - } -} - -func TestDeriveName(t *testing.T) { - actual := deriveName("builtin/provisioners", "builtin/provisioners/magic/remote-exec") - expected := "magic-remote-exec" - if actual != expected { - t.Errorf("Expected %s; found %s", expected, actual) - } -} - -func TestDeriveImport(t *testing.T) { - actual := deriveImport("provider", "magic-aws") - expected := "magicawsprovider" - if actual != expected { - t.Errorf("Expected %s; found %s", expected, actual) - } -} - -func contains(plugins []plugin, name string) bool { - for _, plugin := range plugins { - if plugin.PluginName == name { - return true - } - } - return false -} - -//func TestDiscoverTypesProviders(t *testing.T) { -// plugins, err := discoverTypesInPath("../builtin/providers", "terraform.ResourceProvider", "Provider") -// if err != nil { -// t.Fatalf(err.Error()) -// } -// // We're just going to spot-check, not do this exhaustively -// if !contains(plugins, "aws") { -// t.Errorf("Expected to find aws provider") -// } -// if !contains(plugins, "docker") { -// t.Errorf("Expected to find docker provider") -// } -// if !contains(plugins, "dnsimple") { -// t.Errorf("Expected to find dnsimple provider") -// } -// if !contains(plugins, "triton") { -// t.Errorf("Expected to find triton provider") -// } -// if contains(plugins, "file") { -// t.Errorf("Found unexpected provider file") -// } -//} - -func TestDiscoverTypesProvisioners(t *testing.T) { - plugins, err := discoverTypesInPath("../builtin/provisioners", "terraform.ResourceProvisioner", "Provisioner") - if err != nil { - t.Fatalf(err.Error()) - } - if !contains(plugins, "remote-exec") { - t.Errorf("Expected to find remote-exec provisioner") - } - if contains(plugins, "aws") { - t.Errorf("Found unexpected provisioner aws") - } -} diff --git a/terraform/version.go b/terraform/version.go deleted file mode 100644 index 0caeca0ad..000000000 --- a/terraform/version.go +++ /dev/null @@ -1,10 +0,0 @@ -package terraform - -import ( - "github.com/hashicorp/terraform/version" -) - -// Deprecated: Providers should use schema.Provider.TerraformVersion instead -func VersionString() string { - return version.String() -}