From c2469c95f4db03cbd9ca9f1f44a0fb5b7678b629 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Tue, 19 Jul 2016 04:38:00 +0000 Subject: [PATCH] provider/rabbitmq: Initial Commit of RabbitMQ Provider Contains provider configuration, a rabbitmq_vhost resource, and acceptance test. --- .../providers/rabbitmq/import_vhost_test.go | 32 +++++ builtin/providers/rabbitmq/provider.go | 117 ++++++++++++++++++ builtin/providers/rabbitmq/provider_test.go | 46 +++++++ builtin/providers/rabbitmq/resource_vhost.go | 85 +++++++++++++ .../providers/rabbitmq/resource_vhost_test.go | 79 ++++++++++++ builtin/providers/rabbitmq/util.go | 14 +++ command/internal_plugin_list.go | 2 + 7 files changed, 375 insertions(+) create mode 100644 builtin/providers/rabbitmq/import_vhost_test.go create mode 100644 builtin/providers/rabbitmq/provider.go create mode 100644 builtin/providers/rabbitmq/provider_test.go create mode 100644 builtin/providers/rabbitmq/resource_vhost.go create mode 100644 builtin/providers/rabbitmq/resource_vhost_test.go create mode 100644 builtin/providers/rabbitmq/util.go diff --git a/builtin/providers/rabbitmq/import_vhost_test.go b/builtin/providers/rabbitmq/import_vhost_test.go new file mode 100644 index 000000000..599a02436 --- /dev/null +++ b/builtin/providers/rabbitmq/import_vhost_test.go @@ -0,0 +1,32 @@ +package rabbitmq + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccVhost_importBasic(t *testing.T) { + resourceName := "rabbitmq_vhost.test" + var vhost string + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccVhostCheckDestroy(vhost), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVhostConfig_basic, + Check: testAccVhostCheck( + resourceName, &vhost, + ), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/rabbitmq/provider.go b/builtin/providers/rabbitmq/provider.go new file mode 100644 index 000000000..4f270bdce --- /dev/null +++ b/builtin/providers/rabbitmq/provider.go @@ -0,0 +1,117 @@ +package rabbitmq + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net/http" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "endpoint": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("RABBITMQ_ENDPOINT", nil), + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value == "" { + errors = append(errors, fmt.Errorf("Endpoint must not be an empty string")) + } + + return + }, + }, + + "username": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("RABBITMQ_USERNAME", nil), + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value == "" { + errors = append(errors, fmt.Errorf("Username must not be an empty string")) + } + + return + }, + }, + + "password": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("RABBITMQ_PASSWORD", nil), + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value == "" { + errors = append(errors, fmt.Errorf("Password must not be an empty string")) + } + + return + }, + }, + + "insecure": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("RABBITMQ_INSECURE", nil), + }, + + "cacert_file": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("RABBITMQ_CACERT", ""), + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "rabbitmq_vhost": resourceVhost(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + + var username = d.Get("username").(string) + var password = d.Get("password").(string) + var endpoint = d.Get("endpoint").(string) + var insecure = d.Get("insecure").(bool) + var cacertFile = d.Get("cacert_file").(string) + + // Configure TLS/SSL: + // Ignore self-signed cert warnings + // Specify a custom CA / intermediary cert + // Specify a certificate and key + tlsConfig := &tls.Config{} + if cacertFile != "" { + caCert, err := ioutil.ReadFile(cacertFile) + if err != nil { + return nil, err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig.RootCAs = caCertPool + } + if insecure { + tlsConfig.InsecureSkipVerify = true + } + + // Connect to RabbitMQ management interface + transport := &http.Transport{TLSClientConfig: tlsConfig} + rmqc, err := rabbithole.NewTLSClient(endpoint, username, password, transport) + if err != nil { + return nil, err + } + + return rmqc, nil +} diff --git a/builtin/providers/rabbitmq/provider_test.go b/builtin/providers/rabbitmq/provider_test.go new file mode 100644 index 000000000..411459ee8 --- /dev/null +++ b/builtin/providers/rabbitmq/provider_test.go @@ -0,0 +1,46 @@ +package rabbitmq + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +// To run these acceptance tests, you will need access to a RabbitMQ server +// with the management plugin enabled. +// +// Set the RABBITMQ_ENDPOINT, RABBITMQ_USERNAME, and RABBITMQ_PASSWORD +// environment variables before running the tests. +// +// You can run the tests like this: +// make testacc TEST=./builtin/providers/rabbitmq + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "rabbitmq": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func testAccPreCheck(t *testing.T) { + for _, name := range []string{"RABBITMQ_ENDPOINT", "RABBITMQ_USERNAME", "RABBITMQ_PASSWORD"} { + if v := os.Getenv(name); v == "" { + t.Fatal("RABBITMQ_ENDPOINT, RABBITMQ_USERNAME and RABBITMQ_PASSWORD must be set for acceptance tests") + } + } +} diff --git a/builtin/providers/rabbitmq/resource_vhost.go b/builtin/providers/rabbitmq/resource_vhost.go new file mode 100644 index 000000000..d93c80728 --- /dev/null +++ b/builtin/providers/rabbitmq/resource_vhost.go @@ -0,0 +1,85 @@ +package rabbitmq + +import ( + "fmt" + "log" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceVhost() *schema.Resource { + return &schema.Resource{ + Create: CreateVhost, + Read: ReadVhost, + Delete: DeleteVhost, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func CreateVhost(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + vhost := d.Get("name").(string) + + log.Printf("[DEBUG] RabbitMQ: Attempting to create vhost %s", vhost) + + resp, err := rmqc.PutVhost(vhost, rabbithole.VhostSettings{}) + log.Printf("[DEBUG] RabbitMQ: vhost creation response: %#v", resp) + if err != nil { + return err + } + + d.SetId(vhost) + + return ReadVhost(d, meta) +} + +func ReadVhost(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + vhost, err := rmqc.GetVhost(d.Id()) + if err != nil { + return checkDeleted(d, err) + } + + log.Printf("[DEBUG] RabbitMQ: Vhost retrieved: %#v", vhost) + + d.Set("name", vhost.Name) + + return nil +} + +func DeleteVhost(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + log.Printf("[DEBUG] RabbitMQ: Attempting to delete vhost %s", d.Id()) + + resp, err := rmqc.DeleteVhost(d.Id()) + log.Printf("[DEBUG] RabbitMQ: vhost deletion response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode == 404 { + // the vhost was automatically deleted + return nil + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error deleting RabbitMQ user: %s", resp.Status) + } + + return nil +} diff --git a/builtin/providers/rabbitmq/resource_vhost_test.go b/builtin/providers/rabbitmq/resource_vhost_test.go new file mode 100644 index 000000000..76f6a2734 --- /dev/null +++ b/builtin/providers/rabbitmq/resource_vhost_test.go @@ -0,0 +1,79 @@ +package rabbitmq + +import ( + "fmt" + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccVhost(t *testing.T) { + var vhost string + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccVhostCheckDestroy(vhost), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVhostConfig_basic, + Check: testAccVhostCheck( + "rabbitmq_vhost.test", &vhost, + ), + }, + }, + }) +} + +func testAccVhostCheck(rn string, name *string) 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("vhost id not set") + } + + rmqc := testAccProvider.Meta().(*rabbithole.Client) + vhosts, err := rmqc.ListVhosts() + if err != nil { + return fmt.Errorf("Error retrieving vhosts: %s", err) + } + + for _, vhost := range vhosts { + if vhost.Name == rs.Primary.ID { + *name = rs.Primary.ID + return nil + } + } + + return fmt.Errorf("Unable to find vhost %s", rn) + } +} + +func testAccVhostCheckDestroy(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rmqc := testAccProvider.Meta().(*rabbithole.Client) + vhosts, err := rmqc.ListVhosts() + if err != nil { + return fmt.Errorf("Error retrieving vhosts: %s", err) + } + + for _, vhost := range vhosts { + if vhost.Name == name { + return fmt.Errorf("vhost still exists: %s", vhost) + } + } + + return nil + } +} + +const testAccVhostConfig_basic = ` +resource "rabbitmq_vhost" "test" { + name = "test" +}` diff --git a/builtin/providers/rabbitmq/util.go b/builtin/providers/rabbitmq/util.go new file mode 100644 index 000000000..604d47dce --- /dev/null +++ b/builtin/providers/rabbitmq/util.go @@ -0,0 +1,14 @@ +package rabbitmq + +import ( + "github.com/hashicorp/terraform/helper/schema" +) + +func checkDeleted(d *schema.ResourceData, err error) error { + if err.Error() == "not found" { + d.SetId("") + return nil + } + + return err +} diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go index e3fc743c3..eea391a2b 100644 --- a/command/internal_plugin_list.go +++ b/command/internal_plugin_list.go @@ -38,6 +38,7 @@ import ( packetprovider "github.com/hashicorp/terraform/builtin/providers/packet" postgresqlprovider "github.com/hashicorp/terraform/builtin/providers/postgresql" powerdnsprovider "github.com/hashicorp/terraform/builtin/providers/powerdns" + rabbitmqprovider "github.com/hashicorp/terraform/builtin/providers/rabbitmq" randomprovider "github.com/hashicorp/terraform/builtin/providers/random" rundeckprovider "github.com/hashicorp/terraform/builtin/providers/rundeck" scalewayprovider "github.com/hashicorp/terraform/builtin/providers/scaleway" @@ -93,6 +94,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{ "packet": packetprovider.Provider, "postgresql": postgresqlprovider.Provider, "powerdns": powerdnsprovider.Provider, + "rabbitmq": rabbitmqprovider.Provider, "random": randomprovider.Provider, "rundeck": rundeckprovider.Provider, "scaleway": scalewayprovider.Provider,