From c952513aed919dfba9065eb0a983818cb267ccf0 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 9 Jul 2014 11:13:05 -0700 Subject: [PATCH] rpc: Adding support for provisioners --- rpc/resource_provisioner.go | 106 ++++++++++++++++++++++++ rpc/resource_provisioner_test.go | 138 +++++++++++++++++++++++++++++++ rpc/rpc.go | 3 + 3 files changed, 247 insertions(+) create mode 100644 rpc/resource_provisioner.go create mode 100644 rpc/resource_provisioner_test.go diff --git a/rpc/resource_provisioner.go b/rpc/resource_provisioner.go new file mode 100644 index 0000000000..dc8a0ac167 --- /dev/null +++ b/rpc/resource_provisioner.go @@ -0,0 +1,106 @@ +package rpc + +import ( + "github.com/hashicorp/terraform/terraform" + "net/rpc" +) + +// ResourceProvisioner is an implementation of terraform.ResourceProvisioner +// that communicates over RPC. +type ResourceProvisioner struct { + Client *rpc.Client + Name string +} + +func (p *ResourceProvisioner) Validate(c *terraform.ResourceConfig) ([]string, []error) { + var resp ResourceProvisionerValidateResponse + args := ResourceProvisionerValidateArgs{ + Config: c, + } + + err := p.Client.Call(p.Name+".Validate", &args, &resp) + if err != nil { + return nil, []error{err} + } + + var errs []error + if len(resp.Errors) > 0 { + errs = make([]error, len(resp.Errors)) + for i, err := range resp.Errors { + errs[i] = err + } + } + + return resp.Warnings, errs +} + +func (p *ResourceProvisioner) Apply( + s *terraform.ResourceState, + c *terraform.ResourceConfig) (*terraform.ResourceState, error) { + var resp ResourceProvisionerApplyResponse + args := &ResourceProvisionerApplyArgs{ + State: s, + Config: c, + } + + err := p.Client.Call(p.Name+".Apply", args, &resp) + if err != nil { + return nil, err + } + if resp.Error != nil { + err = resp.Error + } + + return resp.State, err +} + +type ResourceProvisionerValidateArgs struct { + Config *terraform.ResourceConfig +} + +type ResourceProvisionerValidateResponse struct { + Warnings []string + Errors []*BasicError +} + +type ResourceProvisionerApplyArgs struct { + State *terraform.ResourceState + Config *terraform.ResourceConfig +} + +type ResourceProvisionerApplyResponse struct { + State *terraform.ResourceState + Error *BasicError +} + +// ResourceProvisionerServer is a net/rpc compatible structure for serving +// a ResourceProvisioner. This should not be used directly. +type ResourceProvisionerServer struct { + Provisioner terraform.ResourceProvisioner +} + +func (s *ResourceProvisionerServer) Apply( + args *ResourceProvisionerApplyArgs, + result *ResourceProvisionerApplyResponse) error { + state, err := s.Provisioner.Apply(args.State, args.Config) + *result = ResourceProvisionerApplyResponse{ + State: state, + Error: NewBasicError(err), + } + return nil +} + +func (s *ResourceProvisionerServer) Validate( + args *ResourceProvisionerValidateArgs, + reply *ResourceProvisionerValidateResponse) error { + warns, errs := s.Provisioner.Validate(args.Config) + berrs := make([]*BasicError, len(errs)) + for i, err := range errs { + berrs[i] = NewBasicError(err) + } + *reply = ResourceProvisionerValidateResponse{ + Warnings: warns, + Errors: berrs, + } + return nil +} diff --git a/rpc/resource_provisioner_test.go b/rpc/resource_provisioner_test.go new file mode 100644 index 0000000000..73f11eb9a7 --- /dev/null +++ b/rpc/resource_provisioner_test.go @@ -0,0 +1,138 @@ +package rpc + +import ( + "errors" + "reflect" + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestResourceProvisioner_impl(t *testing.T) { + var _ terraform.ResourceProvisioner = new(ResourceProvisioner) +} + +func TestResourceProvisioner_apply(t *testing.T) { + p := new(terraform.MockResourceProvisioner) + client, server := testClientServer(t) + name, err := Register(server, p) + if err != nil { + t.Fatalf("err: %s", err) + } + provisioner := &ResourceProvisioner{Client: client, Name: name} + + p.ApplyReturn = &terraform.ResourceState{ + ID: "bob", + } + + // Apply + state := &terraform.ResourceState{} + conf := &terraform.ResourceConfig{} + newState, err := provisioner.Apply(state, conf) + if !p.ApplyCalled { + t.Fatal("apply should be called") + } + if !reflect.DeepEqual(p.ApplyConfig, conf) { + t.Fatalf("bad: %#v", p.ApplyConfig) + } + if err != nil { + t.Fatalf("bad: %#v", err) + } + if !reflect.DeepEqual(p.ApplyReturn, newState) { + t.Fatalf("bad: %#v", newState) + } +} + +func TestResourceProvisioner_validate(t *testing.T) { + p := new(terraform.MockResourceProvisioner) + client, server := testClientServer(t) + name, err := Register(server, p) + if err != nil { + t.Fatalf("err: %s", err) + } + provisioner := &ResourceProvisioner{Client: client, Name: name} + + // Configure + config := &terraform.ResourceConfig{ + Raw: map[string]interface{}{"foo": "bar"}, + } + w, e := provisioner.Validate(config) + if !p.ValidateCalled { + t.Fatal("configure should be called") + } + if !reflect.DeepEqual(p.ValidateConfig, config) { + t.Fatalf("bad: %#v", p.ValidateConfig) + } + if w != nil { + t.Fatalf("bad: %#v", w) + } + if e != nil { + t.Fatalf("bad: %#v", e) + } +} + +func TestResourceProvisioner_validate_errors(t *testing.T) { + p := new(terraform.MockResourceProvisioner) + p.ValidateReturnErrors = []error{errors.New("foo")} + + client, server := testClientServer(t) + name, err := Register(server, p) + if err != nil { + t.Fatalf("err: %s", err) + } + provisioner := &ResourceProvisioner{Client: client, Name: name} + + // Configure + config := &terraform.ResourceConfig{ + Raw: map[string]interface{}{"foo": "bar"}, + } + w, e := provisioner.Validate(config) + if !p.ValidateCalled { + t.Fatal("configure should be called") + } + if !reflect.DeepEqual(p.ValidateConfig, config) { + t.Fatalf("bad: %#v", p.ValidateConfig) + } + if w != nil { + t.Fatalf("bad: %#v", w) + } + + if len(e) != 1 { + t.Fatalf("bad: %#v", e) + } + if e[0].Error() != "foo" { + t.Fatalf("bad: %#v", e) + } +} + +func TestResourceProvisioner_validate_warns(t *testing.T) { + p := new(terraform.MockResourceProvisioner) + p.ValidateReturnWarns = []string{"foo"} + + client, server := testClientServer(t) + name, err := Register(server, p) + if err != nil { + t.Fatalf("err: %s", err) + } + provisioner := &ResourceProvisioner{Client: client, Name: name} + + // Configure + config := &terraform.ResourceConfig{ + Raw: map[string]interface{}{"foo": "bar"}, + } + w, e := provisioner.Validate(config) + if !p.ValidateCalled { + t.Fatal("configure should be called") + } + if !reflect.DeepEqual(p.ValidateConfig, config) { + t.Fatalf("bad: %#v", p.ValidateConfig) + } + if e != nil { + t.Fatalf("bad: %#v", e) + } + + expected := []string{"foo"} + if !reflect.DeepEqual(w, expected) { + t.Fatalf("bad: %#v", w) + } +} diff --git a/rpc/rpc.go b/rpc/rpc.go index 5f178ae939..f11a482f34 100644 --- a/rpc/rpc.go +++ b/rpc/rpc.go @@ -23,6 +23,9 @@ func Register(server *rpc.Server, thing interface{}) (name string, err error) { case terraform.ResourceProvider: name = fmt.Sprintf("Terraform%d", nextId) err = server.RegisterName(name, &ResourceProviderServer{Provider: t}) + case terraform.ResourceProvisioner: + name = fmt.Sprintf("Terraform%d", nextId) + err = server.RegisterName(name, &ResourceProvisionerServer{Provisioner: t}) default: return "", errors.New("Unknown type to register for RPC server.") }