rpc: Speak new API with UIInput

This commit is contained in:
Mitchell Hashimoto 2014-09-29 00:23:17 -07:00
parent 5611b9b8a8
commit caf8e372f2
7 changed files with 242 additions and 8 deletions

View File

@ -82,6 +82,7 @@ func (c *Client) ResourceProvider() (terraform.ResourceProvider, error) {
} }
return &ResourceProvider{ return &ResourceProvider{
Broker: c.broker,
Client: rpc.NewClient(conn), Client: rpc.NewClient(conn),
Name: "ResourceProvider", Name: "ResourceProvider",
}, nil }, nil

View File

@ -9,10 +9,37 @@ import (
// ResourceProvider is an implementation of terraform.ResourceProvider // ResourceProvider is an implementation of terraform.ResourceProvider
// that communicates over RPC. // that communicates over RPC.
type ResourceProvider struct { type ResourceProvider struct {
Broker *muxBroker
Client *rpc.Client Client *rpc.Client
Name string Name string
} }
func (p *ResourceProvider) Input(
input terraform.UIInput,
c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
id := p.Broker.NextId()
go acceptAndServe(p.Broker, id, "UIInput", &UIInputServer{
UIInput: input,
})
var resp ResourceProviderInputResponse
args := ResourceProviderInputArgs{
InputId: id,
Config: c,
}
err := p.Client.Call(p.Name+".Input", &args, &resp)
if err != nil {
return nil, err
}
if resp.Error != nil {
err = resp.Error
return nil, err
}
return resp.Config, nil
}
func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) { func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
var resp ResourceProviderValidateResponse var resp ResourceProviderValidateResponse
args := ResourceProviderValidateArgs{ args := ResourceProviderValidateArgs{
@ -150,6 +177,7 @@ func (p *ResourceProvider) Resources() []terraform.ResourceType {
// ResourceProviderServer is a net/rpc compatible structure for serving // ResourceProviderServer is a net/rpc compatible structure for serving
// a ResourceProvider. This should not be used directly. // a ResourceProvider. This should not be used directly.
type ResourceProviderServer struct { type ResourceProviderServer struct {
Broker *muxBroker
Provider terraform.ResourceProvider Provider terraform.ResourceProvider
} }
@ -157,6 +185,16 @@ type ResourceProviderConfigureResponse struct {
Error *BasicError Error *BasicError
} }
type ResourceProviderInputArgs struct {
InputId uint32
Config *terraform.ResourceConfig
}
type ResourceProviderInputResponse struct {
Config *terraform.ResourceConfig
Error *BasicError
}
type ResourceProviderApplyArgs struct { type ResourceProviderApplyArgs struct {
Info *terraform.InstanceInfo Info *terraform.InstanceInfo
State *terraform.InstanceState State *terraform.InstanceState
@ -208,6 +246,33 @@ type ResourceProviderValidateResourceResponse struct {
Errors []*BasicError Errors []*BasicError
} }
func (s *ResourceProviderServer) Input(
args *ResourceProviderInputArgs,
reply *ResourceProviderInputResponse) error {
conn, err := s.Broker.Dial(args.InputId)
if err != nil {
*reply = ResourceProviderInputResponse{
Error: NewBasicError(err),
}
return nil
}
client := rpc.NewClient(conn)
defer client.Close()
input := &UIInput{
Client: client,
Name: "UIInput",
}
config, err := s.Provider.Input(input, args.Config)
*reply = ResourceProviderInputResponse{
Config: config,
Error: NewBasicError(err),
}
return nil
}
func (s *ResourceProviderServer) Validate( func (s *ResourceProviderServer) Validate(
args *ResourceProviderValidateArgs, args *ResourceProviderValidateArgs,
reply *ResourceProviderValidateResponse) error { reply *ResourceProviderValidateResponse) error {

View File

@ -12,14 +12,54 @@ func TestResourceProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = new(ResourceProvider) var _ terraform.ResourceProvider = new(ResourceProvider)
} }
func TestResourceProvider_configure(t *testing.T) { func TestResourceProvider_input(t *testing.T) {
p := new(terraform.MockResourceProvider) client, server := testNewClientServer(t)
client, server := testClientServer(t) defer client.Close()
name, err := Register(server, p)
p := server.ProviderFunc().(*terraform.MockResourceProvider)
provider, err := client.ResourceProvider()
if err != nil {
t.Fatalf("err: %s", err)
}
input := new(terraform.MockUIInput)
expected := &terraform.ResourceConfig{
Raw: map[string]interface{}{"bar": "baz"},
}
p.InputReturnConfig = expected
// Input
config := &terraform.ResourceConfig{
Raw: map[string]interface{}{"foo": "bar"},
}
actual, err := provider.Input(input, config)
if !p.InputCalled {
t.Fatal("input should be called")
}
if !reflect.DeepEqual(p.InputConfig, config) {
t.Fatalf("bad: %#v", p.InputConfig)
}
if err != nil {
t.Fatalf("bad: %#v", err)
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestResourceProvider_configure(t *testing.T) {
client, server := testNewClientServer(t)
defer client.Close()
p := server.ProviderFunc().(*terraform.MockResourceProvider)
provider, err := client.ResourceProvider()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
provider := &ResourceProvider{Client: client, Name: name}
// Configure // Configure
config := &terraform.ResourceConfig{ config := &terraform.ResourceConfig{

View File

@ -46,6 +46,24 @@ func testClientServer(t *testing.T) (*rpc.Client, *rpc.Server) {
return client, server return client, server
} }
func testNewClientServer(t *testing.T) (*Client, *Server) {
clientConn, serverConn := testConn(t)
server := &Server{
ProviderFunc: testProviderFixed(new(terraform.MockResourceProvider)),
ProvisionerFunc: testProvisionerFixed(
new(terraform.MockResourceProvisioner)),
}
go server.ServeConn(serverConn)
client, err := NewClient(clientConn)
if err != nil {
t.Fatalf("err: %s", err)
}
return client, server
}
func testProviderFixed(p terraform.ResourceProvider) ProviderFunc { func testProviderFixed(p terraform.ResourceProvider) ProviderFunc {
return func() terraform.ResourceProvider { return func() terraform.ResourceProvider {
return p return p

View File

@ -96,7 +96,8 @@ func (d *dispenseServer) ResourceProvider(
return return
} }
d.serve(conn, "ResourceProvider", &ResourceProviderServer{ serve(conn, "ResourceProvider", &ResourceProviderServer{
Broker: d.broker,
Provider: d.ProviderFunc(), Provider: d.ProviderFunc(),
}) })
}() }()
@ -116,7 +117,7 @@ func (d *dispenseServer) ResourceProvisioner(
return return
} }
d.serve(conn, "ResourceProvisioner", &ResourceProvisionerServer{ serve(conn, "ResourceProvisioner", &ResourceProvisionerServer{
Provisioner: d.ProvisionerFunc(), Provisioner: d.ProvisionerFunc(),
}) })
}() }()
@ -124,7 +125,17 @@ func (d *dispenseServer) ResourceProvisioner(
return nil return nil
} }
func (d *dispenseServer) serve(conn io.ReadWriteCloser, name string, v interface{}) { func acceptAndServe(mux *muxBroker, id uint32, n string, v interface{}) {
conn, err := mux.Accept(id)
if err != nil {
log.Printf("[ERR] Plugin acceptAndServe: %s", err)
return
}
serve(conn, n, v)
}
func serve(conn io.ReadWriteCloser, name string, v interface{}) {
server := rpc.NewServer() server := rpc.NewServer()
if err := server.RegisterName(name, v); err != nil { if err := server.RegisterName(name, v); err != nil {
log.Printf("[ERR] Plugin dispense: %s", err) log.Printf("[ERR] Plugin dispense: %s", err)

51
rpc/ui_input.go Normal file
View File

@ -0,0 +1,51 @@
package rpc
import (
"net/rpc"
"github.com/hashicorp/terraform/terraform"
)
// UIInput is an implementatin of terraform.UIInput that communicates
// over RPC.
type UIInput struct {
Client *rpc.Client
Name string
}
func (i *UIInput) Input(opts *terraform.InputOpts) (string, error) {
var resp UIInputInputResponse
err := i.Client.Call(i.Name+".Input", opts, &resp)
if err != nil {
return "", err
}
if resp.Error != nil {
err = resp.Error
return "", err
}
return resp.Value, nil
}
type UIInputInputResponse struct {
Value string
Error *BasicError
}
// UIInputServer is a net/rpc compatible structure for serving
// a UIInputServer. This should not be used directly.
type UIInputServer struct {
UIInput terraform.UIInput
}
func (s *UIInputServer) Input(
opts *terraform.InputOpts,
reply *UIInputInputResponse) error {
value, err := s.UIInput.Input(opts)
*reply = UIInputInputResponse{
Value: value,
Error: NewBasicError(err),
}
return nil
}

48
rpc/ui_input_test.go Normal file
View File

@ -0,0 +1,48 @@
package rpc
import (
"reflect"
"testing"
"github.com/hashicorp/terraform/terraform"
)
func TestUIInput_impl(t *testing.T) {
var _ terraform.UIInput = new(UIInput)
}
func TestUIInput_input(t *testing.T) {
client, server := testClientServer(t)
defer client.Close()
i := new(terraform.MockUIInput)
i.InputReturnString = "foo"
err := server.RegisterName("UIInput", &UIInputServer{
UIInput: i,
})
if err != nil {
t.Fatalf("err: %s", err)
}
input := &UIInput{Client: client, Name: "UIInput"}
opts := &terraform.InputOpts{
Id: "foo",
}
v, err := input.Input(opts)
if !i.InputCalled {
t.Fatal("input should be called")
}
if !reflect.DeepEqual(i.InputOpts, opts) {
t.Fatalf("bad: %#v", i.InputOpts)
}
if err != nil {
t.Fatalf("bad: %#v", err)
}
if v != "foo" {
t.Fatalf("bad: %#v", v)
}
}