diff --git a/builtin/providers/rabbitmq/import_binding_test.go b/builtin/providers/rabbitmq/import_binding_test.go new file mode 100644 index 000000000..db845c710 --- /dev/null +++ b/builtin/providers/rabbitmq/import_binding_test.go @@ -0,0 +1,34 @@ +package rabbitmq + +import ( + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccBinding_importBasic(t *testing.T) { + resourceName := "rabbitmq_binding.test" + var bindingInfo rabbithole.BindingInfo + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccBindingCheckDestroy(bindingInfo), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccBindingConfig_basic, + Check: testAccBindingCheck( + resourceName, &bindingInfo, + ), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/rabbitmq/provider.go b/builtin/providers/rabbitmq/provider.go index 790e6e29f..601aa3b15 100644 --- a/builtin/providers/rabbitmq/provider.go +++ b/builtin/providers/rabbitmq/provider.go @@ -72,6 +72,7 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ + "rabbitmq_binding": resourceBinding(), "rabbitmq_exchange": resourceExchange(), "rabbitmq_permissions": resourcePermissions(), "rabbitmq_queue": resourceQueue(), diff --git a/builtin/providers/rabbitmq/resource_binding.go b/builtin/providers/rabbitmq/resource_binding.go new file mode 100644 index 000000000..dff6bf009 --- /dev/null +++ b/builtin/providers/rabbitmq/resource_binding.go @@ -0,0 +1,195 @@ +package rabbitmq + +import ( + "fmt" + "log" + "strings" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceBinding() *schema.Resource { + return &schema.Resource{ + Create: CreateBinding, + Read: ReadBinding, + Delete: DeleteBinding, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "source": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "vhost": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "destination": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "destination_type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "properties_key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "routing_key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "arguments": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func CreateBinding(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + vhost := d.Get("vhost").(string) + bindingInfo := rabbithole.BindingInfo{ + Source: d.Get("source").(string), + Destination: d.Get("destination").(string), + DestinationType: d.Get("destination_type").(string), + RoutingKey: d.Get("routing_key").(string), + PropertiesKey: d.Get("properties_key").(string), + Arguments: d.Get("arguments").(map[string]interface{}), + } + + if err := declareBinding(rmqc, vhost, bindingInfo); err != nil { + return err + } + + name := fmt.Sprintf("%s/%s/%s/%s/%s", vhost, bindingInfo.Source, bindingInfo.Destination, bindingInfo.DestinationType, bindingInfo.PropertiesKey) + d.SetId(name) + + return ReadBinding(d, meta) +} + +func ReadBinding(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + bindingId := strings.Split(d.Id(), "/") + if len(bindingId) < 5 { + return fmt.Errorf("Unable to determine binding ID") + } + + vhost := bindingId[0] + source := bindingId[1] + destination := bindingId[2] + destinationType := bindingId[3] + propertiesKey := bindingId[4] + + bindings, err := rmqc.ListBindingsIn(vhost) + if err != nil { + return err + } + + log.Printf("[DEBUG] RabbitMQ: Bindings retrieved: %#v", bindings) + bindingFound := false + for _, binding := range bindings { + if binding.Source == source && binding.Destination == destination && binding.DestinationType == destinationType && binding.PropertiesKey == propertiesKey { + log.Printf("[DEBUG] RabbitMQ: Found Binding: %#v", binding) + bindingFound = true + + d.Set("vhost", binding.Vhost) + d.Set("source", binding.Source) + d.Set("destination", binding.Destination) + d.Set("destination_type", binding.DestinationType) + d.Set("routing_key", binding.RoutingKey) + d.Set("properties_key", binding.PropertiesKey) + d.Set("arguments", binding.Arguments) + } + } + + // The binding could not be found, + // so consider it deleted and remove from state + if !bindingFound { + d.SetId("") + } + + return nil +} + +func DeleteBinding(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + bindingId := strings.Split(d.Id(), "/") + if len(bindingId) < 5 { + return fmt.Errorf("Unable to determine binding ID") + } + + vhost := bindingId[0] + source := bindingId[1] + destination := bindingId[2] + destinationType := bindingId[3] + propertiesKey := bindingId[4] + + bindingInfo := rabbithole.BindingInfo{ + Vhost: vhost, + Source: source, + Destination: destination, + DestinationType: destinationType, + PropertiesKey: propertiesKey, + } + + log.Printf("[DEBUG] RabbitMQ: Attempting to delete binding for %s/%s/%s/%s/%s", + vhost, source, destination, destinationType, propertiesKey) + + resp, err := rmqc.DeleteBinding(vhost, bindingInfo) + if err != nil { + return err + } + + log.Printf("[DEBUG] RabbitMQ: Binding delete response: %#v", resp) + + if resp.StatusCode == 404 { + // The binding was already deleted + return nil + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error deleting RabbitMQ binding: %s", resp.Status) + } + + return nil +} + +func declareBinding(rmqc *rabbithole.Client, vhost string, bindingInfo rabbithole.BindingInfo) error { + log.Printf("[DEBUG] RabbitMQ: Attempting to declare binding for %s/%s/%s/%s/%s", + vhost, bindingInfo.Source, bindingInfo.Destination, bindingInfo.DestinationType, bindingInfo.PropertiesKey) + + resp, err := rmqc.DeclareBinding(vhost, bindingInfo) + log.Printf("[DEBUG] RabbitMQ: Binding declare response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error declaring RabbitMQ binding: %s", resp.Status) + } + + return nil +} diff --git a/builtin/providers/rabbitmq/resource_binding_test.go b/builtin/providers/rabbitmq/resource_binding_test.go new file mode 100644 index 000000000..8b710f98c --- /dev/null +++ b/builtin/providers/rabbitmq/resource_binding_test.go @@ -0,0 +1,121 @@ +package rabbitmq + +import ( + "fmt" + "strings" + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccBinding(t *testing.T) { + var bindingInfo rabbithole.BindingInfo + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccBindingCheckDestroy(bindingInfo), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccBindingConfig_basic, + Check: testAccBindingCheck( + "rabbitmq_binding.test", &bindingInfo, + ), + }, + }, + }) +} + +func testAccBindingCheck(rn string, bindingInfo *rabbithole.BindingInfo) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("resource not found: %s", rn) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("binding id not set") + } + + rmqc := testAccProvider.Meta().(*rabbithole.Client) + bindingParts := strings.Split(rs.Primary.ID, "/") + + bindings, err := rmqc.ListBindingsIn(bindingParts[0]) + if err != nil { + return fmt.Errorf("Error retrieving exchange: %s", err) + } + + for _, binding := range bindings { + if binding.Source == bindingParts[1] && binding.Destination == bindingParts[2] && binding.DestinationType == bindingParts[3] && binding.PropertiesKey == bindingParts[4] { + bindingInfo = &binding + return nil + } + } + + return fmt.Errorf("Unable to find binding %s", rn) + } +} + +func testAccBindingCheckDestroy(bindingInfo rabbithole.BindingInfo) resource.TestCheckFunc { + return func(s *terraform.State) error { + rmqc := testAccProvider.Meta().(*rabbithole.Client) + + bindings, err := rmqc.ListBindingsIn(bindingInfo.Vhost) + if err != nil { + return fmt.Errorf("Error retrieving exchange: %s", err) + } + + for _, binding := range bindings { + if binding.Source == bindingInfo.Source && binding.Destination == bindingInfo.Destination && binding.DestinationType == bindingInfo.DestinationType && binding.PropertiesKey == bindingInfo.PropertiesKey { + return fmt.Errorf("Binding still exists") + } + } + + return nil + } +} + +const testAccBindingConfig_basic = ` +resource "rabbitmq_vhost" "test" { + name = "test" +} + +resource "rabbitmq_permissions" "guest" { + user = "guest" + vhost = "${rabbitmq_vhost.test.name}" + permissions { + configure = ".*" + write = ".*" + read = ".*" + } +} + +resource "rabbitmq_exchange" "test" { + name = "test" + vhost = "${rabbitmq_permissions.guest.vhost}" + settings { + type = "fanout" + durable = false + auto_delete = true + } +} + +resource "rabbitmq_queue" "test" { + name = "test" + vhost = "${rabbitmq_permissions.guest.vhost}" + settings { + durable = true + auto_delete = false + } +} + +resource "rabbitmq_binding" "test" { + source = "${rabbitmq_exchange.test.name}" + vhost = "${rabbitmq_vhost.test.name}" + destination = "${rabbitmq_queue.test.name}" + destination_type = "queue" + routing_key = "#" + properties_key = "%23" +}`