diff --git a/builtin/providers/rabbitmq/acceptance_env/deploy.sh b/builtin/providers/rabbitmq/acceptance_env/deploy.sh new file mode 100644 index 000000000..037750866 --- /dev/null +++ b/builtin/providers/rabbitmq/acceptance_env/deploy.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +cd +echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list +wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add - +sudo apt-get update +sudo apt-get install -y git make mercurial +sudo apt-get install -y rabbitmq-server +sudo rabbitmq-plugins enable rabbitmq_management + +sudo wget -O /usr/local/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme +sudo chmod +x /usr/local/bin/gimme +gimme 1.6 >> .bashrc + +mkdir ~/go +eval "$(/usr/local/bin/gimme 1.6)" +echo 'export GOPATH=$HOME/go' >> .bashrc +export GOPATH=$HOME/go + +export PATH=$PATH:$HOME/terraform:$HOME/go/bin +echo 'export PATH=$PATH:$HOME/terraform:$HOME/go/bin' >> .bashrc +source .bashrc + +go get -u github.com/kardianos/govendor +go get github.com/hashicorp/terraform 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/import_exchange_test.go b/builtin/providers/rabbitmq/import_exchange_test.go new file mode 100644 index 000000000..8fb8ac53b --- /dev/null +++ b/builtin/providers/rabbitmq/import_exchange_test.go @@ -0,0 +1,34 @@ +package rabbitmq + +import ( + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccExchange_importBasic(t *testing.T) { + resourceName := "rabbitmq_exchange.test" + var exchangeInfo rabbithole.ExchangeInfo + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccExchangeCheckDestroy(&exchangeInfo), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccExchangeConfig_basic, + Check: testAccExchangeCheck( + resourceName, &exchangeInfo, + ), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/rabbitmq/import_permissions_test.go b/builtin/providers/rabbitmq/import_permissions_test.go new file mode 100644 index 000000000..f8d45b063 --- /dev/null +++ b/builtin/providers/rabbitmq/import_permissions_test.go @@ -0,0 +1,34 @@ +package rabbitmq + +import ( + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccPermissions_importBasic(t *testing.T) { + resourceName := "rabbitmq_permissions.test" + var permissionInfo rabbithole.PermissionInfo + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccPermissionsCheckDestroy(&permissionInfo), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPermissionsConfig_basic, + Check: testAccPermissionsCheck( + resourceName, &permissionInfo, + ), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/rabbitmq/import_policy_test.go b/builtin/providers/rabbitmq/import_policy_test.go new file mode 100644 index 000000000..7fa59ba88 --- /dev/null +++ b/builtin/providers/rabbitmq/import_policy_test.go @@ -0,0 +1,34 @@ +package rabbitmq + +import ( + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccPolicy_importBasic(t *testing.T) { + resourceName := "rabbitmq_policy.test" + var policy rabbithole.Policy + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccPolicyCheckDestroy(&policy), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPolicyConfig_basic, + Check: testAccPolicyCheck( + resourceName, &policy, + ), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/rabbitmq/import_queue_test.go b/builtin/providers/rabbitmq/import_queue_test.go new file mode 100644 index 000000000..ceb3eec80 --- /dev/null +++ b/builtin/providers/rabbitmq/import_queue_test.go @@ -0,0 +1,34 @@ +package rabbitmq + +import ( + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccQueue_importBasic(t *testing.T) { + resourceName := "rabbitmq_queue.test" + var queue rabbithole.QueueInfo + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccQueueCheckDestroy(&queue), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccQueueConfig_basic, + Check: testAccQueueCheck( + resourceName, &queue, + ), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/rabbitmq/import_user_test.go b/builtin/providers/rabbitmq/import_user_test.go new file mode 100644 index 000000000..f33eb7e8e --- /dev/null +++ b/builtin/providers/rabbitmq/import_user_test.go @@ -0,0 +1,33 @@ +package rabbitmq + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccUser_importBasic(t *testing.T) { + resourceName := "rabbitmq_user.test" + var user string + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccUserCheckDestroy(user), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccUserConfig_basic, + Check: testAccUserCheck( + resourceName, &user, + ), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password"}, + }, + }, + }) +} 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..f7db53a4b --- /dev/null +++ b/builtin/providers/rabbitmq/provider.go @@ -0,0 +1,123 @@ +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_binding": resourceBinding(), + "rabbitmq_exchange": resourceExchange(), + "rabbitmq_permissions": resourcePermissions(), + "rabbitmq_policy": resourcePolicy(), + "rabbitmq_queue": resourceQueue(), + "rabbitmq_user": resourceUser(), + "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_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" +}` diff --git a/builtin/providers/rabbitmq/resource_exchange.go b/builtin/providers/rabbitmq/resource_exchange.go new file mode 100644 index 000000000..238a59e74 --- /dev/null +++ b/builtin/providers/rabbitmq/resource_exchange.go @@ -0,0 +1,189 @@ +package rabbitmq + +import ( + "fmt" + "log" + "strings" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceExchange() *schema.Resource { + return &schema.Resource{ + Create: CreateExchange, + Read: ReadExchange, + Delete: DeleteExchange, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "vhost": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "/", + ForceNew: true, + }, + + "settings": &schema.Schema{ + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "durable": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "auto_delete": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "arguments": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func CreateExchange(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + name := d.Get("name").(string) + vhost := d.Get("vhost").(string) + settingsList := d.Get("settings").([]interface{}) + + settingsMap, ok := settingsList[0].(map[string]interface{}) + if !ok { + return fmt.Errorf("Unable to parse settings") + } + + if err := declareExchange(rmqc, vhost, name, settingsMap); err != nil { + return err + } + + id := fmt.Sprintf("%s@%s", name, vhost) + d.SetId(id) + + return ReadExchange(d, meta) +} + +func ReadExchange(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + exchangeId := strings.Split(d.Id(), "@") + if len(exchangeId) < 2 { + return fmt.Errorf("Unable to determine exchange ID") + } + + name := exchangeId[0] + vhost := exchangeId[1] + + exchangeSettings, err := rmqc.GetExchange(vhost, name) + if err != nil { + return checkDeleted(d, err) + } + + log.Printf("[DEBUG] RabbitMQ: Exchange retrieved %s: %#v", d.Id(), exchangeSettings) + + d.Set("name", exchangeSettings.Name) + d.Set("vhost", exchangeSettings.Vhost) + + exchange := make([]map[string]interface{}, 1) + e := make(map[string]interface{}) + e["type"] = exchangeSettings.Type + e["durable"] = exchangeSettings.Durable + e["auto_delete"] = exchangeSettings.AutoDelete + e["arguments"] = exchangeSettings.Arguments + exchange[0] = e + d.Set("settings", exchange) + + return nil +} + +func DeleteExchange(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + exchangeId := strings.Split(d.Id(), "@") + if len(exchangeId) < 2 { + return fmt.Errorf("Unable to determine exchange ID") + } + + name := exchangeId[0] + vhost := exchangeId[1] + + log.Printf("[DEBUG] RabbitMQ: Attempting to delete exchange %s", d.Id()) + + resp, err := rmqc.DeleteExchange(vhost, name) + log.Printf("[DEBUG] RabbitMQ: Exchange delete response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode == 404 { + // The exchange was automatically deleted + return nil + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error deleting RabbitMQ exchange: %s", resp.Status) + } + + return nil +} + +func declareExchange(rmqc *rabbithole.Client, vhost string, name string, settingsMap map[string]interface{}) error { + exchangeSettings := rabbithole.ExchangeSettings{} + + if v, ok := settingsMap["type"].(string); ok { + exchangeSettings.Type = v + } + + if v, ok := settingsMap["durable"].(bool); ok { + exchangeSettings.Durable = v + } + + if v, ok := settingsMap["auto_delete"].(bool); ok { + exchangeSettings.AutoDelete = v + } + + if v, ok := settingsMap["arguments"].(map[string]interface{}); ok { + exchangeSettings.Arguments = v + } + + log.Printf("[DEBUG] RabbitMQ: Attempting to declare exchange %s@%s: %#v", name, vhost, exchangeSettings) + + resp, err := rmqc.DeclareExchange(vhost, name, exchangeSettings) + log.Printf("[DEBUG] RabbitMQ: Exchange declare response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error declaring RabbitMQ exchange: %s", resp.Status) + } + + return nil +} diff --git a/builtin/providers/rabbitmq/resource_exchange_test.go b/builtin/providers/rabbitmq/resource_exchange_test.go new file mode 100644 index 000000000..7747e489d --- /dev/null +++ b/builtin/providers/rabbitmq/resource_exchange_test.go @@ -0,0 +1,103 @@ +package rabbitmq + +import ( + "fmt" + "strings" + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccExchange(t *testing.T) { + var exchangeInfo rabbithole.ExchangeInfo + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccExchangeCheckDestroy(&exchangeInfo), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccExchangeConfig_basic, + Check: testAccExchangeCheck( + "rabbitmq_exchange.test", &exchangeInfo, + ), + }, + }, + }) +} + +func testAccExchangeCheck(rn string, exchangeInfo *rabbithole.ExchangeInfo) 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("exchange id not set") + } + + rmqc := testAccProvider.Meta().(*rabbithole.Client) + exchParts := strings.Split(rs.Primary.ID, "@") + + exchanges, err := rmqc.ListExchangesIn(exchParts[1]) + if err != nil { + return fmt.Errorf("Error retrieving exchange: %s", err) + } + + for _, exchange := range exchanges { + if exchange.Name == exchParts[0] && exchange.Vhost == exchParts[1] { + exchangeInfo = &exchange + return nil + } + } + + return fmt.Errorf("Unable to find exchange %s", rn) + } +} + +func testAccExchangeCheckDestroy(exchangeInfo *rabbithole.ExchangeInfo) resource.TestCheckFunc { + return func(s *terraform.State) error { + rmqc := testAccProvider.Meta().(*rabbithole.Client) + + exchanges, err := rmqc.ListExchangesIn(exchangeInfo.Vhost) + if err != nil { + return fmt.Errorf("Error retrieving exchange: %s", err) + } + + for _, exchange := range exchanges { + if exchange.Name == exchangeInfo.Name && exchange.Vhost == exchangeInfo.Vhost { + return fmt.Errorf("Exchange %s@%s still exist", exchangeInfo.Name, exchangeInfo.Vhost) + } + } + + return nil + } +} + +const testAccExchangeConfig_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 + } +}` diff --git a/builtin/providers/rabbitmq/resource_permissions.go b/builtin/providers/rabbitmq/resource_permissions.go new file mode 100644 index 000000000..e9ec70818 --- /dev/null +++ b/builtin/providers/rabbitmq/resource_permissions.go @@ -0,0 +1,205 @@ +package rabbitmq + +import ( + "fmt" + "log" + "strings" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePermissions() *schema.Resource { + return &schema.Resource{ + Create: CreatePermissions, + Update: UpdatePermissions, + Read: ReadPermissions, + Delete: DeletePermissions, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "user": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "vhost": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "/", + ForceNew: true, + }, + + "permissions": &schema.Schema{ + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "configure": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "write": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "read": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + } +} + +func CreatePermissions(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + user := d.Get("user").(string) + vhost := d.Get("vhost").(string) + permsList := d.Get("permissions").([]interface{}) + + permsMap, ok := permsList[0].(map[string]interface{}) + if !ok { + return fmt.Errorf("Unable to parse permissions") + } + + if err := setPermissionsIn(rmqc, vhost, user, permsMap); err != nil { + return err + } + + id := fmt.Sprintf("%s@%s", user, vhost) + d.SetId(id) + + return ReadPermissions(d, meta) +} + +func ReadPermissions(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + permissionId := strings.Split(d.Id(), "@") + if len(permissionId) < 2 { + return fmt.Errorf("Unable to determine Permission ID") + } + + user := permissionId[0] + vhost := permissionId[1] + + userPerms, err := rmqc.GetPermissionsIn(vhost, user) + if err != nil { + return checkDeleted(d, err) + } + + log.Printf("[DEBUG] RabbitMQ: Permission retrieved for %s: %#v", d.Id(), userPerms) + + d.Set("user", userPerms.User) + d.Set("vhost", userPerms.Vhost) + + perms := make([]map[string]interface{}, 1) + p := make(map[string]interface{}) + p["configure"] = userPerms.Configure + p["write"] = userPerms.Write + p["read"] = userPerms.Read + perms[0] = p + d.Set("permissions", perms) + + return nil +} + +func UpdatePermissions(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + permissionId := strings.Split(d.Id(), "@") + if len(permissionId) < 2 { + return fmt.Errorf("Unable to determine Permission ID") + } + + user := permissionId[0] + vhost := permissionId[1] + + if d.HasChange("permissions") { + _, newPerms := d.GetChange("permissions") + + newPermsList := newPerms.([]interface{}) + permsMap, ok := newPermsList[0].(map[string]interface{}) + if !ok { + return fmt.Errorf("Unable to parse permissions") + } + + if err := setPermissionsIn(rmqc, vhost, user, permsMap); err != nil { + return err + } + } + + return ReadPermissions(d, meta) +} + +func DeletePermissions(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + permissionId := strings.Split(d.Id(), "@") + if len(permissionId) < 2 { + return fmt.Errorf("Unable to determine Permission ID") + } + + user := permissionId[0] + vhost := permissionId[1] + + log.Printf("[DEBUG] RabbitMQ: Attempting to delete permission for %s", d.Id()) + + resp, err := rmqc.ClearPermissionsIn(vhost, user) + log.Printf("[DEBUG] RabbitMQ: Permission delete response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode == 404 { + // The permissions were already deleted + return nil + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error deleting RabbitMQ permission: %s", resp.Status) + } + + return nil +} + +func setPermissionsIn(rmqc *rabbithole.Client, vhost string, user string, permsMap map[string]interface{}) error { + perms := rabbithole.Permissions{} + + if v, ok := permsMap["configure"].(string); ok { + perms.Configure = v + } + + if v, ok := permsMap["write"].(string); ok { + perms.Write = v + } + + if v, ok := permsMap["read"].(string); ok { + perms.Read = v + } + + log.Printf("[DEBUG] RabbitMQ: Attempting to set permissions for %s@%s: %#v", user, vhost, perms) + + resp, err := rmqc.UpdatePermissionsIn(vhost, user, perms) + log.Printf("[DEBUG] RabbitMQ: Permission response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error setting permissions: %s", resp.Status) + } + + return nil +} diff --git a/builtin/providers/rabbitmq/resource_permissions_test.go b/builtin/providers/rabbitmq/resource_permissions_test.go new file mode 100644 index 000000000..3d87adf1e --- /dev/null +++ b/builtin/providers/rabbitmq/resource_permissions_test.go @@ -0,0 +1,124 @@ +package rabbitmq + +import ( + "fmt" + "strings" + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPermissions(t *testing.T) { + var permissionInfo rabbithole.PermissionInfo + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccPermissionsCheckDestroy(&permissionInfo), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPermissionsConfig_basic, + Check: testAccPermissionsCheck( + "rabbitmq_permissions.test", &permissionInfo, + ), + }, + resource.TestStep{ + Config: testAccPermissionsConfig_update, + Check: testAccPermissionsCheck( + "rabbitmq_permissions.test", &permissionInfo, + ), + }, + }, + }) +} + +func testAccPermissionsCheck(rn string, permissionInfo *rabbithole.PermissionInfo) 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("permission id not set") + } + + rmqc := testAccProvider.Meta().(*rabbithole.Client) + perms, err := rmqc.ListPermissions() + if err != nil { + return fmt.Errorf("Error retrieving permissions: %s", err) + } + + userParts := strings.Split(rs.Primary.ID, "@") + for _, perm := range perms { + if perm.User == userParts[0] && perm.Vhost == userParts[1] { + permissionInfo = &perm + return nil + } + } + + return fmt.Errorf("Unable to find permissions for user %s", rn) + } +} + +func testAccPermissionsCheckDestroy(permissionInfo *rabbithole.PermissionInfo) resource.TestCheckFunc { + return func(s *terraform.State) error { + rmqc := testAccProvider.Meta().(*rabbithole.Client) + perms, err := rmqc.ListPermissions() + if err != nil { + return fmt.Errorf("Error retrieving permissions: %s", err) + } + + for _, perm := range perms { + if perm.User == permissionInfo.User && perm.Vhost == permissionInfo.Vhost { + return fmt.Errorf("Permissions still exist for user %s@%s", permissionInfo.User, permissionInfo.Vhost) + } + } + + return nil + } +} + +const testAccPermissionsConfig_basic = ` +resource "rabbitmq_vhost" "test" { + name = "test" +} + +resource "rabbitmq_user" "test" { + name = "mctest" + password = "foobar" + tags = ["administrator"] +} + +resource "rabbitmq_permissions" "test" { + user = "${rabbitmq_user.test.name}" + vhost = "${rabbitmq_vhost.test.name}" + permissions { + configure = ".*" + write = ".*" + read = ".*" + } +}` + +const testAccPermissionsConfig_update = ` +resource "rabbitmq_vhost" "test" { + name = "test" +} + +resource "rabbitmq_user" "test" { + name = "mctest" + password = "foobar" + tags = ["administrator"] +} + +resource "rabbitmq_permissions" "test" { + user = "${rabbitmq_user.test.name}" + vhost = "${rabbitmq_vhost.test.name}" + permissions { + configure = ".*" + write = ".*" + read = "" + } +}` diff --git a/builtin/providers/rabbitmq/resource_policy.go b/builtin/providers/rabbitmq/resource_policy.go new file mode 100644 index 000000000..31154373f --- /dev/null +++ b/builtin/providers/rabbitmq/resource_policy.go @@ -0,0 +1,239 @@ +package rabbitmq + +import ( + "fmt" + "log" + "strings" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePolicy() *schema.Resource { + return &schema.Resource{ + Create: CreatePolicy, + Update: UpdatePolicy, + Read: ReadPolicy, + Delete: DeletePolicy, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "vhost": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "policy": &schema.Schema{ + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pattern": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "priority": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + + "apply_to": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "definition": &schema.Schema{ + Type: schema.TypeMap, + Required: true, + }, + }, + }, + }, + }, + } +} + +func CreatePolicy(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + name := d.Get("name").(string) + vhost := d.Get("vhost").(string) + policyList := d.Get("policy").([]interface{}) + + policyMap, ok := policyList[0].(map[string]interface{}) + if !ok { + return fmt.Errorf("Unable to parse policy") + } + + if err := putPolicy(rmqc, vhost, name, policyMap); err != nil { + return err + } + + id := fmt.Sprintf("%s@%s", name, vhost) + d.SetId(id) + + return ReadPolicy(d, meta) +} + +func ReadPolicy(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + policyId := strings.Split(d.Id(), "@") + if len(policyId) < 2 { + return fmt.Errorf("Unable to determine policy ID") + } + + user := policyId[0] + vhost := policyId[1] + + policy, err := rmqc.GetPolicy(vhost, user) + if err != nil { + return checkDeleted(d, err) + } + + log.Printf("[DEBUG] RabbitMQ: Policy retrieved for %s: %#v", d.Id(), policy) + + d.Set("name", policy.Name) + d.Set("vhost", policy.Vhost) + + setPolicy := make([]map[string]interface{}, 1) + p := make(map[string]interface{}) + p["pattern"] = policy.Pattern + p["priority"] = policy.Priority + p["apply_to"] = policy.ApplyTo + + policyDefinition := make(map[string]interface{}) + for key, value := range policy.Definition { + if v, ok := value.([]interface{}); ok { + var nodes []string + for _, node := range v { + if n, ok := node.(string); ok { + nodes = append(nodes, n) + } + } + value = strings.Join(nodes, ",") + } + policyDefinition[key] = value + } + p["definition"] = policyDefinition + setPolicy[0] = p + + d.Set("policy", setPolicy) + + return nil +} + +func UpdatePolicy(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + policyId := strings.Split(d.Id(), "@") + if len(policyId) < 2 { + return fmt.Errorf("Unable to determine policy ID") + } + + user := policyId[0] + vhost := policyId[1] + + if d.HasChange("policy") { + _, newPolicy := d.GetChange("policy") + + policyList := newPolicy.([]interface{}) + policyMap, ok := policyList[0].(map[string]interface{}) + if !ok { + return fmt.Errorf("Unable to parse policy") + } + + if err := putPolicy(rmqc, user, vhost, policyMap); err != nil { + return err + } + } + + return ReadPolicy(d, meta) +} + +func DeletePolicy(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + policyId := strings.Split(d.Id(), "@") + if len(policyId) < 2 { + return fmt.Errorf("Unable to determine policy ID") + } + + user := policyId[0] + vhost := policyId[1] + + log.Printf("[DEBUG] RabbitMQ: Attempting to delete policy for %s", d.Id()) + + resp, err := rmqc.DeletePolicy(vhost, user) + log.Printf("[DEBUG] RabbitMQ: Policy delete response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode == 404 { + // the policy was automatically deleted + return nil + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error deleting RabbitMQ policy: %s", resp.Status) + } + + return nil +} + +func putPolicy(rmqc *rabbithole.Client, vhost string, name string, policyMap map[string]interface{}) error { + policy := rabbithole.Policy{} + policy.Vhost = vhost + policy.Name = name + + if v, ok := policyMap["pattern"].(string); ok { + policy.Pattern = v + } + + if v, ok := policyMap["priority"].(int); ok { + policy.Priority = v + } + + if v, ok := policyMap["apply_to"].(string); ok { + policy.ApplyTo = v + } + + if v, ok := policyMap["definition"].(map[string]interface{}); ok { + // special case for ha-mode = nodes + if x, ok := v["ha-mode"]; ok && x == "nodes" { + var nodes rabbithole.NodeNames + nodes = strings.Split(v["ha-params"].(string), ",") + v["ha-params"] = nodes + } + policyDefinition := rabbithole.PolicyDefinition{} + policyDefinition = v + policy.Definition = policyDefinition + } + + log.Printf("[DEBUG] RabbitMQ: Attempting to declare policy for %s@%s: %#v", name, vhost, policy) + + resp, err := rmqc.PutPolicy(vhost, name, policy) + log.Printf("[DEBUG] RabbitMQ: Policy declare response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error declaring RabbitMQ policy: %s", resp.Status) + } + + return nil +} diff --git a/builtin/providers/rabbitmq/resource_policy_test.go b/builtin/providers/rabbitmq/resource_policy_test.go new file mode 100644 index 000000000..614620b69 --- /dev/null +++ b/builtin/providers/rabbitmq/resource_policy_test.go @@ -0,0 +1,141 @@ +package rabbitmq + +import ( + "fmt" + "strings" + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPolicy(t *testing.T) { + var policy rabbithole.Policy + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccPolicyCheckDestroy(&policy), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPolicyConfig_basic, + Check: testAccPolicyCheck( + "rabbitmq_policy.test", &policy, + ), + }, + resource.TestStep{ + Config: testAccPolicyConfig_update, + Check: testAccPolicyCheck( + "rabbitmq_policy.test", &policy, + ), + }, + }, + }) +} + +func testAccPolicyCheck(rn string, policy *rabbithole.Policy) 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("policy id not set") + } + + rmqc := testAccProvider.Meta().(*rabbithole.Client) + policyParts := strings.Split(rs.Primary.ID, "@") + + policies, err := rmqc.ListPolicies() + if err != nil { + return fmt.Errorf("Error retrieving policies: %s", err) + } + + for _, p := range policies { + if p.Name == policyParts[0] && p.Vhost == policyParts[1] { + policy = &p + return nil + } + } + + return fmt.Errorf("Unable to find policy %s", rn) + } +} + +func testAccPolicyCheckDestroy(policy *rabbithole.Policy) resource.TestCheckFunc { + return func(s *terraform.State) error { + rmqc := testAccProvider.Meta().(*rabbithole.Client) + + policies, err := rmqc.ListPolicies() + if err != nil { + return fmt.Errorf("Error retrieving policies: %s", err) + } + + for _, p := range policies { + if p.Name == policy.Name && p.Vhost == policy.Vhost { + return fmt.Errorf("Policy %s@%s still exist", policy.Name, policy.Vhost) + } + } + + return nil + } +} + +const testAccPolicyConfig_basic = ` +resource "rabbitmq_vhost" "test" { + name = "test" +} + +resource "rabbitmq_permissions" "guest" { + user = "guest" + vhost = "${rabbitmq_vhost.test.name}" + permissions { + configure = ".*" + write = ".*" + read = ".*" + } +} + +resource "rabbitmq_policy" "test" { + name = "test" + vhost = "${rabbitmq_permissions.guest.vhost}" + policy { + pattern = ".*" + priority = 0 + apply_to = "all" + definition { + ha-mode = "nodes" + ha-params = "a,b,c" + } + } +}` + +const testAccPolicyConfig_update = ` +resource "rabbitmq_vhost" "test" { + name = "test" +} + +resource "rabbitmq_permissions" "guest" { + user = "guest" + vhost = "${rabbitmq_vhost.test.name}" + permissions { + configure = ".*" + write = ".*" + read = ".*" + } +} + +resource "rabbitmq_policy" "test" { + name = "test" + vhost = "${rabbitmq_permissions.guest.vhost}" + policy { + pattern = ".*" + priority = 0 + apply_to = "all" + definition { + ha-mode = "all" + } + } +}` diff --git a/builtin/providers/rabbitmq/resource_queue.go b/builtin/providers/rabbitmq/resource_queue.go new file mode 100644 index 000000000..097b04d62 --- /dev/null +++ b/builtin/providers/rabbitmq/resource_queue.go @@ -0,0 +1,180 @@ +package rabbitmq + +import ( + "fmt" + "log" + "strings" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceQueue() *schema.Resource { + return &schema.Resource{ + Create: CreateQueue, + Read: ReadQueue, + Delete: DeleteQueue, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "vhost": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "/", + ForceNew: true, + }, + + "settings": &schema.Schema{ + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "durable": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "auto_delete": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "arguments": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func CreateQueue(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + name := d.Get("name").(string) + vhost := d.Get("vhost").(string) + settingsList := d.Get("settings").([]interface{}) + + settingsMap, ok := settingsList[0].(map[string]interface{}) + if !ok { + return fmt.Errorf("Unable to parse settings") + } + + if err := declareQueue(rmqc, vhost, name, settingsMap); err != nil { + return err + } + + id := fmt.Sprintf("%s@%s", name, vhost) + d.SetId(id) + + return ReadQueue(d, meta) +} + +func ReadQueue(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + queueId := strings.Split(d.Id(), "@") + if len(queueId) < 2 { + return fmt.Errorf("Unable to determine Queue ID") + } + + user := queueId[0] + vhost := queueId[1] + + queueSettings, err := rmqc.GetQueue(vhost, user) + if err != nil { + return checkDeleted(d, err) + } + + log.Printf("[DEBUG] RabbitMQ: Queue retrieved for %s: %#v", d.Id(), queueSettings) + + d.Set("name", queueSettings.Name) + d.Set("vhost", queueSettings.Vhost) + + queue := make([]map[string]interface{}, 1) + e := make(map[string]interface{}) + e["durable"] = queueSettings.Durable + e["auto_delete"] = queueSettings.AutoDelete + e["arguments"] = queueSettings.Arguments + queue[0] = e + + d.Set("settings", queue) + + return nil +} + +func DeleteQueue(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + queueId := strings.Split(d.Id(), "@") + if len(queueId) < 2 { + return fmt.Errorf("Unable to determine Queue ID") + } + + user := queueId[0] + vhost := queueId[1] + + log.Printf("[DEBUG] RabbitMQ: Attempting to delete queue for %s", d.Id()) + + resp, err := rmqc.DeleteQueue(vhost, user) + log.Printf("[DEBUG] RabbitMQ: Queue delete response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode == 404 { + // the queue was automatically deleted + return nil + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error deleting RabbitMQ queue: %s", resp.Status) + } + + return nil +} + +func declareQueue(rmqc *rabbithole.Client, vhost string, name string, settingsMap map[string]interface{}) error { + queueSettings := rabbithole.QueueSettings{} + + if v, ok := settingsMap["durable"].(bool); ok { + queueSettings.Durable = v + } + + if v, ok := settingsMap["auto_delete"].(bool); ok { + queueSettings.AutoDelete = v + } + + if v, ok := settingsMap["arguments"].(map[string]interface{}); ok { + queueSettings.Arguments = v + } + + log.Printf("[DEBUG] RabbitMQ: Attempting to declare queue for %s@%s: %#v", name, vhost, queueSettings) + + resp, err := rmqc.DeclareQueue(vhost, name, queueSettings) + log.Printf("[DEBUG] RabbitMQ: Queue declare response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error declaring RabbitMQ queue: %s", resp.Status) + } + + return nil +} diff --git a/builtin/providers/rabbitmq/resource_queue_test.go b/builtin/providers/rabbitmq/resource_queue_test.go new file mode 100644 index 000000000..4d63104c6 --- /dev/null +++ b/builtin/providers/rabbitmq/resource_queue_test.go @@ -0,0 +1,102 @@ +package rabbitmq + +import ( + "fmt" + "strings" + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccQueue(t *testing.T) { + var queueInfo rabbithole.QueueInfo + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccQueueCheckDestroy(&queueInfo), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccQueueConfig_basic, + Check: testAccQueueCheck( + "rabbitmq_queue.test", &queueInfo, + ), + }, + }, + }) +} + +func testAccQueueCheck(rn string, queueInfo *rabbithole.QueueInfo) 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("queue id not set") + } + + rmqc := testAccProvider.Meta().(*rabbithole.Client) + queueParts := strings.Split(rs.Primary.ID, "@") + + queues, err := rmqc.ListQueuesIn(queueParts[1]) + if err != nil { + return fmt.Errorf("Error retrieving queue: %s", err) + } + + for _, queue := range queues { + if queue.Name == queueParts[0] && queue.Vhost == queueParts[1] { + queueInfo = &queue + return nil + } + } + + return fmt.Errorf("Unable to find queue %s", rn) + } +} + +func testAccQueueCheckDestroy(queueInfo *rabbithole.QueueInfo) resource.TestCheckFunc { + return func(s *terraform.State) error { + rmqc := testAccProvider.Meta().(*rabbithole.Client) + + queues, err := rmqc.ListQueuesIn(queueInfo.Vhost) + if err != nil { + return fmt.Errorf("Error retrieving queue: %s", err) + } + + for _, queue := range queues { + if queue.Name == queueInfo.Name && queue.Vhost == queueInfo.Vhost { + return fmt.Errorf("Queue %s@%s still exist", queueInfo.Name, queueInfo.Vhost) + } + } + + return nil + } +} + +const testAccQueueConfig_basic = ` +resource "rabbitmq_vhost" "test" { + name = "test" +} + +resource "rabbitmq_permissions" "guest" { + user = "guest" + vhost = "${rabbitmq_vhost.test.name}" + permissions { + configure = ".*" + write = ".*" + read = ".*" + } +} + +resource "rabbitmq_queue" "test" { + name = "test" + vhost = "${rabbitmq_permissions.guest.vhost}" + settings { + durable = false + auto_delete = true + } +}` diff --git a/builtin/providers/rabbitmq/resource_user.go b/builtin/providers/rabbitmq/resource_user.go new file mode 100644 index 000000000..2177182ef --- /dev/null +++ b/builtin/providers/rabbitmq/resource_user.go @@ -0,0 +1,174 @@ +package rabbitmq + +import ( + "fmt" + "log" + "strings" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceUser() *schema.Resource { + return &schema.Resource{ + Create: CreateUser, + Update: UpdateUser, + Read: ReadUser, + Delete: DeleteUser, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "password": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + + "tags": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func CreateUser(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + name := d.Get("name").(string) + + var tagList []string + for _, v := range d.Get("tags").([]interface{}) { + tagList = append(tagList, v.(string)) + } + tags := strings.Join(tagList, ",") + + userSettings := rabbithole.UserSettings{ + Password: d.Get("password").(string), + Tags: tags, + } + + log.Printf("[DEBUG] RabbitMQ: Attempting to create user %s", name) + + resp, err := rmqc.PutUser(name, userSettings) + log.Printf("[DEBUG] RabbitMQ: user creation response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error creating RabbitMQ user: %s", resp.Status) + } + + d.SetId(name) + + return ReadUser(d, meta) +} + +func ReadUser(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + user, err := rmqc.GetUser(d.Id()) + if err != nil { + return checkDeleted(d, err) + } + + log.Printf("[DEBUG] RabbitMQ: User retrieved: %#v", user) + + d.Set("name", user.Name) + + tags := strings.Split(user.Tags, ",") + d.Set("tags", tags) + + return nil +} + +func UpdateUser(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + name := d.Id() + + if d.HasChange("password") { + _, newPassword := d.GetChange("password") + + userSettings := rabbithole.UserSettings{ + Password: newPassword.(string), + } + + log.Printf("[DEBUG] RabbitMQ: Attempting to update password for %s", name) + + resp, err := rmqc.PutUser(name, userSettings) + log.Printf("[DEBUG] RabbitMQ: Password update response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error updating RabbitMQ user: %s", resp.Status) + } + + } + + if d.HasChange("tags") { + _, newTags := d.GetChange("tags") + + var tagList []string + for _, v := range newTags.([]interface{}) { + tagList = append(tagList, v.(string)) + } + tags := strings.Join(tagList, ",") + + userSettings := rabbithole.UserSettings{ + Tags: tags, + } + + log.Printf("[DEBUG] RabbitMQ: Attempting to update tags for %s", name) + + resp, err := rmqc.PutUser(name, userSettings) + log.Printf("[DEBUG] RabbitMQ: Tags update response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode >= 400 { + return fmt.Errorf("Error updating RabbitMQ user: %s", resp.Status) + } + + } + + return ReadUser(d, meta) +} + +func DeleteUser(d *schema.ResourceData, meta interface{}) error { + rmqc := meta.(*rabbithole.Client) + + name := d.Id() + log.Printf("[DEBUG] RabbitMQ: Attempting to delete user %s", name) + + resp, err := rmqc.DeleteUser(name) + log.Printf("[DEBUG] RabbitMQ: User delete response: %#v", resp) + if err != nil { + return err + } + + if resp.StatusCode == 404 { + // the user 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_user_test.go b/builtin/providers/rabbitmq/resource_user_test.go new file mode 100644 index 000000000..6876134bf --- /dev/null +++ b/builtin/providers/rabbitmq/resource_user_test.go @@ -0,0 +1,94 @@ +package rabbitmq + +import ( + "fmt" + "testing" + + "github.com/michaelklishin/rabbit-hole" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccUser(t *testing.T) { + var user string + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccUserCheckDestroy(user), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccUserConfig_basic, + Check: testAccUserCheck( + "rabbitmq_user.test", &user, + ), + }, + resource.TestStep{ + Config: testAccUserConfig_update, + Check: testAccUserCheck( + "rabbitmq_user.test", &user, + ), + }, + }, + }) +} + +func testAccUserCheck(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("user id not set") + } + + rmqc := testAccProvider.Meta().(*rabbithole.Client) + users, err := rmqc.ListUsers() + if err != nil { + return fmt.Errorf("Error retrieving users: %s", err) + } + + for _, user := range users { + if user.Name == rs.Primary.ID { + *name = rs.Primary.ID + return nil + } + } + + return fmt.Errorf("Unable to find user %s", rn) + } +} + +func testAccUserCheckDestroy(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rmqc := testAccProvider.Meta().(*rabbithole.Client) + users, err := rmqc.ListUsers() + if err != nil { + return fmt.Errorf("Error retrieving users: %s", err) + } + + for _, user := range users { + if user.Name == name { + return fmt.Errorf("user still exists: %s", name) + } + } + + return nil + } +} + +const testAccUserConfig_basic = ` +resource "rabbitmq_user" "test" { + name = "mctest" + password = "foobar" + tags = ["administrator", "management"] +}` + +const testAccUserConfig_update = ` +resource "rabbitmq_user" "test" { + name = "mctest" + password = "foobarry" + tags = ["management"] +}` 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, diff --git a/vendor/github.com/michaelklishin/rabbit-hole/CONTRIBUTING.md b/vendor/github.com/michaelklishin/rabbit-hole/CONTRIBUTING.md new file mode 100644 index 000000000..a0d6f6eef --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/CONTRIBUTING.md @@ -0,0 +1,20 @@ +## Contributing + +The workflow is pretty standard: + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push -u origin my-new-feature`) +5. Create new Pull Request + +## Running Tests + +First run `bin/ci/before_build.sh` that will create a vhost and user(s) needed +by the test suite. + +The project uses [Ginkgo](http://onsi.github.io/ginkgo/) and [Gomega](https://github.com/onsi/gomega). + +To clone dependencies and run tests, use `make`. It is also possible +to use the brilliant [Ginkgo CLI runner](http://onsi.github.io/ginkgo/#the-ginkgo-cli) e.g. +to only run a subset of tests. diff --git a/vendor/github.com/michaelklishin/rabbit-hole/ChangeLog.md b/vendor/github.com/michaelklishin/rabbit-hole/ChangeLog.md new file mode 100644 index 000000000..b5252ffd7 --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/ChangeLog.md @@ -0,0 +1,32 @@ +## Changes Between 1.0.0 and 1.1.0 (unreleased) + +### More Complete Message Stats Information + +Message stats now include fields such as `deliver_get` and `redeliver`. + +GH issue: [#73](https://github.com/michaelklishin/rabbit-hole/pull/73). + +Contributed by Edward Wilde. + + +## 1.0 (first tagged release, Dec 25th, 2015) + +### TLS Support + +`rabbithole.NewTLSClient` is a new function which works +much like `NewClient` but additionally accepts a transport. + +Contributed by @[GrimTheReaper](https://github.com/GrimTheReaper). + +### Federation Support + +It is now possible to create federation links +over HTTP API. + +Contributed by [Ryan Grenz](https://github.com/grenzr-bskyb). + +### Core Operations Support + +Most common HTTP API operations (listing and management of +vhosts, users, permissions, queues, exchanges, and bindings) +are supported by the client. diff --git a/vendor/github.com/michaelklishin/rabbit-hole/LICENSE b/vendor/github.com/michaelklishin/rabbit-hole/LICENSE new file mode 100644 index 000000000..420e1f802 --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2013, Michael Klishin +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/github.com/michaelklishin/rabbit-hole/Makefile b/vendor/github.com/michaelklishin/rabbit-hole/Makefile new file mode 100644 index 000000000..c3efcfb3e --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/Makefile @@ -0,0 +1,26 @@ +export GOPATH := $(CURDIR) + +COVER_FILE := coverage + +all: test + +.PHONY: test + +test: install-dependencies + go test -v + +cover: install-dependencies install-cover + go test -v -test.coverprofile="$(COVER_FILE).prof" + sed -i.bak 's|_'$(GOPATH)'|.|g' $(COVER_FILE).prof + go tool cover -html=$(COVER_FILE).prof -o $(COVER_FILE).html + rm $(COVER_FILE).prof* + +install-cover: + go get code.google.com/p/go.tools/cmd/cover + +install-dependencies: + go get github.com/onsi/ginkgo + go get github.com/onsi/gomega + go get github.com/streadway/amqp + # to get Ginkgo CLI + go install github.com/onsi/ginkgo/ginkgo diff --git a/vendor/github.com/michaelklishin/rabbit-hole/README.md b/vendor/github.com/michaelklishin/rabbit-hole/README.md new file mode 100644 index 000000000..a847492ec --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/README.md @@ -0,0 +1,296 @@ +# Rabbit Hole, a RabbitMQ HTTP API Client for Go + +This library is a [RabbitMQ HTTP API](https://raw.githack.com/rabbitmq/rabbitmq-management/rabbitmq_v3_6_0/priv/www/api/index.html) client for the Go language. + +## Supported Go Versions + +Rabbit Hole requires Go 1.3+. + + +## Supported RabbitMQ Versions + + * RabbitMQ 3.x + +All versions require [RabbitMQ Management UI plugin](http://www.rabbitmq.com/management.html) to be installed and enabled. + + +## Project Maturity + +Rabbit Hole is a fairly mature library (started in October 2013) +designed after a couple of other RabbitMQ HTTP API clients with stable +APIs. Breaking API changes are not out of the question but not without +a reasonable version bump. + +It is largely (80-90%) feature complete and decently documented. + + +## Installation + +``` +go get github.com/michaelklishin/rabbit-hole +``` + + +## Documentation + +### Overview + +To import the package: + +``` go +import ( + "github.com/michaelklishin/rabbit-hole" +) +``` + +All HTTP API operations are accessible via `rabbithole.Client`, which +should be instantiated with `rabbithole.NewClient`: + +``` go +// URI, username, password +rmqc, _ = NewClient("http://127.0.0.1:15672", "guest", "guest") +``` + +SSL/TSL is now available, by adding a Transport Layer to the parameters +of `rabbithole.NewTLSClient`: +``` go +transport := &http.Transport{TLSClientConfig: tlsConfig} +rmqc, _ := NewTLSClient("https://127.0.0.1:15672", "guest", "guest", transport) +``` +However, RabbitMQ-Management does not have SSL/TLS enabled by default, +so you must enable it. + +[API reference](http://godoc.org/github.com/michaelklishin/rabbit-hole) is available on [godoc.org](http://godoc.org). + + +### Getting Overview + +``` go +res, err := rmqc.Overview() +``` + +### Node and Cluster Status + +``` go +xs, err := rmqc.ListNodes() +// => []NodeInfo, err + +node, err := rmqc.GetNode("rabbit@mercurio") +// => NodeInfo, err +``` + + +### Operations on Connections + +``` go +xs, err := rmqc.ListConnections() +// => []ConnectionInfo, err + +conn, err := rmqc.GetConnection("127.0.0.1:50545 -> 127.0.0.1:5672") +// => ConnectionInfo, err + +// Forcefully close connection +_, err := rmqc.CloseConnection("127.0.0.1:50545 -> 127.0.0.1:5672") +// => *http.Response, err +``` + + +### Operations on Channels + +``` go +xs, err := rmqc.ListChannels() +// => []ChannelInfo, err + +ch, err := rmqc.GetChannel("127.0.0.1:50545 -> 127.0.0.1:5672 (1)") +// => ChannelInfo, err +``` + + +### Operations on Vhosts + +``` go +xs, err := rmqc.ListVhosts() +// => []VhostInfo, err + +// information about individual vhost +x, err := rmqc.GetVhost("/") +// => VhostInfo, err + +// creates or updates individual vhost +resp, err := rmqc.PutVhost("/", VhostSettings{Tracing: false}) +// => *http.Response, err + +// deletes individual vhost +resp, err := rmqc.DeleteVhost("/") +// => *http.Response, err +``` + + +### Managing Users + +``` go +xs, err := rmqc.ListUsers() +// => []UserInfo, err + +// information about individual user +x, err := rmqc.GetUser("my.user") +// => UserInfo, err + +// creates or updates individual user +resp, err := rmqc.PutUser("my.user", UserSettings{Password: "s3krE7", Tags: "management,policymaker"}) +// => *http.Response, err + +// deletes individual user +resp, err := rmqc.DeleteUser("my.user") +// => *http.Response, err +``` + + +### Managing Permissions + +``` go +xs, err := rmqc.ListPermissions() +// => []PermissionInfo, err + +// permissions of individual user +x, err := rmqc.ListPermissionsOf("my.user") +// => []PermissionInfo, err + +// permissions of individual user in vhost +x, err := rmqc.GetPermissionsIn("/", "my.user") +// => PermissionInfo, err + +// updates permissions of user in vhost +resp, err := rmqc.UpdatePermissionsIn("/", "my.user", Permissions{Configure: ".*", Write: ".*", Read: ".*"}) +// => *http.Response, err + +// revokes permissions in vhost +resp, err := rmqc.ClearPermissionsIn("/", "my.user") +// => *http.Response, err +``` + + +### Operations on Exchanges + +``` go +xs, err := rmqc.ListExchanges() +// => []ExchangeInfo, err + +// list exchanges in a vhost +xs, err := rmqc.ListExchangesIn("/") +// => []ExchangeInfo, err + +// information about individual exchange +x, err := rmqc.GetExchange("/", "amq.fanout") +// => ExchangeInfo, err + +// declares an exchange +resp, err := rmqc.DeclareExchange("/", "an.exchange", ExchangeSettings{Type: "fanout", Durable: false}) +// => *http.Response, err + +// deletes individual exchange +resp, err := rmqc.DeleteExchange("/", "an.exchange") +// => *http.Response, err +``` + + +### Operations on Queues + +``` go +qs, err := rmqc.ListQueues() +// => []QueueInfo, err + +// list queues in a vhost +qs, err := rmqc.ListQueuesIn("/") +// => []QueueInfo, err + +// information about individual queue +q, err := rmqc.GetQueue("/", "a.queue") +// => QueueInfo, err + +// declares a queue +resp, err := rmqc.DeclareQueue("/", "a.queue", QueueSettings{Durable: false}) +// => *http.Response, err + +// deletes individual queue +resp, err := rmqc.DeleteQueue("/", "a.queue") +// => *http.Response, err + +// purges all messages in queue +resp, err := rmqc.PurgeQueue("/", "a.queue") +// => *http.Response, err +``` + + +### Operations on Bindings + +``` go +bs, err := rmqc.ListBindings() +// => []BindingInfo, err + +// list bindings in a vhost +bs, err := rmqc.ListBindingsIn("/") +// => []BindingInfo, err + +// list bindings of a queue +bs, err := rmqc.ListQueueBindings("/", "a.queue") +// => []BindingInfo, err + +// declare a binding +resp, err := rmqc.DeclareBinding("/", BindingInfo{ + Source: "an.exchange", + Destination: "a.queue", + DestinationType: "queue", + RoutingKey: "#", +}) +// => *http.Response, err + +// deletes individual binding +resp, err := rmqc.DeleteBinding("/", BindingInfo{ + Source: "an.exchange", + Destination: "a.queue", + DestinationType: "queue", + RoutingKey: "#", + PropertiesKey: "%23", +}) +// => *http.Response, err +``` + +### HTTPS Connections + +``` go +var tlsConfig *tls.Config + +... + +transport := &http.Transport{TLSClientConfig: tlsConfig} + +rmqc, err := NewTLSClient("https://127.0.0.1:15672", "guest", "guest", transport) +``` + +### Changing Transport Layer + +``` go +var transport *http.Transport + +... + +rmqc.SetTransport(transport) +``` + + +## CI Status + +[![Build Status](https://travis-ci.org/michaelklishin/rabbit-hole.svg?branch=master)](https://travis-ci.org/michaelklishin/rabbit-hole) + + +## Contributing + +See [CONTRIBUTING.md](https://github.com/michaelklishin/rabbit-hole/blob/master/CONTRIBUTING.md) + + +## License & Copyright + +2-clause BSD license. + +(c) Michael S. Klishin, 2013-2016. diff --git a/vendor/github.com/michaelklishin/rabbit-hole/bindings.go b/vendor/github.com/michaelklishin/rabbit-hole/bindings.go new file mode 100644 index 000000000..aa7642288 --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/bindings.go @@ -0,0 +1,157 @@ +package rabbithole + +import ( + "encoding/json" + "net/http" + "net/url" +) + +// +// GET /api/bindings +// + +// Example response: +// +// [ +// { +// "source": "", +// "vhost": "\/", +// "destination": "amq.gen-Dzw36tPTm_VsmILY9oTG9w", +// "destination_type": "queue", +// "routing_key": "amq.gen-Dzw36tPTm_VsmILY9oTG9w", +// "arguments": { +// +// }, +// "properties_key": "amq.gen-Dzw36tPTm_VsmILY9oTG9w" +// } +// ] + +type BindingInfo struct { + // Binding source (exchange name) + Source string `json:"source"` + Vhost string `json:"vhost"` + // Binding destination (queue or exchange name) + Destination string `json:"destination"` + // Destination type, either "queue" or "exchange" + DestinationType string `json:"destination_type"` + RoutingKey string `json:"routing_key"` + Arguments map[string]interface{} `json:"arguments"` + PropertiesKey string `json:"properties_key"` +} + +// Returns all bindings +func (c *Client) ListBindings() (rec []BindingInfo, err error) { + req, err := newGETRequest(c, "bindings/") + if err != nil { + return []BindingInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []BindingInfo{}, err + } + + return rec, nil +} + +// +// GET /api/bindings/{vhost} +// + +// Returns all bindings in a virtual host. +func (c *Client) ListBindingsIn(vhost string) (rec []BindingInfo, err error) { + req, err := newGETRequest(c, "bindings/"+url.QueryEscape(vhost)) + if err != nil { + return []BindingInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []BindingInfo{}, err + } + + return rec, nil +} + +// +// GET /api/queues/{vhost}/{queue}/bindings +// + +// Example response: +// [ +// {"source":"", +// "vhost":"/", +// "destination":"amq.gen-H0tnavWatL7g7uU2q5cAPA", +// "destination_type":"queue", +// "routing_key":"amq.gen-H0tnavWatL7g7uU2q5cAPA", +// "arguments":{}, +// "properties_key":"amq.gen-H0tnavWatL7g7uU2q5cAPA"}, +// {"source":"temp", +// "vhost":"/", +// "destination":"amq.gen-H0tnavWatL7g7uU2q5cAPA", +// "destination_type":"queue", +// "routing_key":"", +// "arguments":{}, +// "properties_key":"~"} +// ] + +// Returns all bindings of individual queue. +func (c *Client) ListQueueBindings(vhost, queue string) (rec []BindingInfo, err error) { + req, err := newGETRequest(c, "queues/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(queue)+"/bindings") + if err != nil { + return []BindingInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []BindingInfo{}, err + } + + return rec, nil +} + +// +// POST /api/bindings/{vhost}/e/{source}/{destination_type}/{destination} +// + +// DeclareBinding updates information about a binding between a source and a target +func (c *Client) DeclareBinding(vhost string, info BindingInfo) (res *http.Response, err error) { + info.Vhost = vhost + + if info.Arguments == nil { + info.Arguments = make(map[string]interface{}) + } + body, err := json.Marshal(info) + if err != nil { + return nil, err + } + + req, err := newRequestWithBody(c, "POST", "bindings/"+url.QueryEscape(vhost)+"/e/"+url.QueryEscape(info.Source)+"/"+url.QueryEscape(string(info.DestinationType[0]))+"/"+url.QueryEscape(info.Destination), body) + + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// +// DELETE /api/bindings/{vhost}/e/{source}/{destination_type}/{destination}/{props} +// + +// DeleteBinding delets an individual binding +func (c *Client) DeleteBinding(vhost string, info BindingInfo) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "bindings/"+url.QueryEscape(vhost)+"/e/"+url.QueryEscape(info.Source)+"/"+url.QueryEscape(string(info.DestinationType[0]))+"/"+url.QueryEscape(info.Destination)+"/"+url.QueryEscape(info.PropertiesKey), nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/channels.go b/vendor/github.com/michaelklishin/rabbit-hole/channels.go new file mode 100644 index 000000000..adb1c1946 --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/channels.go @@ -0,0 +1,86 @@ +package rabbithole + +import "net/url" + +// Brief (very incomplete) connection information. +type BriefConnectionDetails struct { + // Connection name + Name string `json:"name"` + // Client port + PeerPort Port `json:"peer_port"` + // Client host + PeerHost string `json:"peer_host"` +} + +type ChannelInfo struct { + // Channel number + Number int `json:"number"` + // Channel name + Name string `json:"name"` + + // basic.qos (prefetch count) value used + PrefetchCount int `json:"prefetch_count"` + // How many consumers does this channel have + ConsumerCount int `json:"consumer_count"` + + // Number of unacknowledged messages on this channel + UnacknowledgedMessageCount int `json:"messages_unacknowledged"` + // Number of messages on this channel unconfirmed to publishers + UnconfirmedMessageCount int `json:"messages_unconfirmed"` + // Number of messages on this channel uncommited to message store + UncommittedMessageCount int `json:"messages_uncommitted"` + // Number of acks on this channel uncommited to message store + UncommittedAckCount int `json:"acks_uncommitted"` + + // TODO(mk): custom deserializer to date/time? + IdleSince string `json:"idle_since"` + + // True if this channel uses publisher confirms + UsesPublisherConfirms bool `json:"confirm"` + // True if this channel uses transactions + Transactional bool `json:"transactional"` + // True if this channel is blocked via channel.flow + ClientFlowBlocked bool `json:"client_flow_blocked"` + + User string `json:"user"` + Vhost string `json:"vhost"` + Node string `json:"node"` + + ConnectionDetails BriefConnectionDetails `json:"connection_details"` +} + +// +// GET /api/channels +// + +// Returns information about all open channels. +func (c *Client) ListChannels() (rec []ChannelInfo, err error) { + req, err := newGETRequest(c, "channels") + if err != nil { + return []ChannelInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []ChannelInfo{}, err + } + + return rec, nil +} + +// +// GET /api/channels/{name} +// + +// Returns channel information. +func (c *Client) GetChannel(name string) (rec *ChannelInfo, err error) { + req, err := newGETRequest(c, "channels/"+url.QueryEscape(name)) + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/client.go b/vendor/github.com/michaelklishin/rabbit-hole/client.go new file mode 100644 index 000000000..7c4f5678d --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/client.go @@ -0,0 +1,132 @@ +package rabbithole + +import ( + "bytes" + "encoding/json" + "errors" + "net/http" + "net/url" +) + +type Client struct { + // URI of a RabbitMQ node to use, not including the path, e.g. http://127.0.0.1:15672. + Endpoint string + // Username to use. This RabbitMQ user must have the "management" tag. + Username string + // Password to use. + Password string + host string + transport *http.Transport +} + +func NewClient(uri string, username string, password string) (me *Client, err error) { + u, err := url.Parse(uri) + if err != nil { + return nil, err + } + + me = &Client{ + Endpoint: uri, + host: u.Host, + Username: username, + Password: password, + } + + return me, nil +} + +//NewTLSClient Creates a Client with a Transport Layer; it is up to the developer to make that layer Secure. +func NewTLSClient(uri string, username string, password string, transport *http.Transport) (me *Client, err error) { + u, err := url.Parse(uri) + if err != nil { + return nil, err + } + + me = &Client{ + Endpoint: uri, + host: u.Host, + Username: username, + Password: password, + transport: transport, + } + + return me, nil +} + +//SetTransport changes the Transport Layer that the Client will use. +func (c *Client) SetTransport(transport *http.Transport) { + c.transport = transport +} + +func newGETRequest(client *Client, path string) (*http.Request, error) { + s := client.Endpoint + "/api/" + path + + req, err := http.NewRequest("GET", s, nil) + + req.Close = true + req.SetBasicAuth(client.Username, client.Password) + // set Opaque to preserve percent-encoded path. MK. + req.URL.Opaque = "//" + client.host + "/api/" + path + + return req, err +} + +func newRequestWithBody(client *Client, method string, path string, body []byte) (*http.Request, error) { + s := client.Endpoint + "/api/" + path + + req, err := http.NewRequest(method, s, bytes.NewReader(body)) + + req.Close = true + req.SetBasicAuth(client.Username, client.Password) + // set Opaque to preserve percent-encoded path. MK. + req.URL.Opaque = "//" + client.host + "/api/" + path + + req.Header.Add("Content-Type", "application/json") + + return req, err +} + +func executeRequest(client *Client, req *http.Request) (res *http.Response, err error) { + var httpc *http.Client + if client.transport != nil { + httpc = &http.Client{Transport: client.transport} + } else { + httpc = &http.Client{} + } + res, err = httpc.Do(req) + + if err != nil { + return nil, err + } + + return res, nil +} + +func executeAndParseRequest(client *Client, req *http.Request, rec interface{}) (err error) { + var httpc *http.Client + if client.transport != nil { + httpc = &http.Client{Transport: client.transport} + } else { + httpc = &http.Client{} + } + res, err := httpc.Do(req) + if err != nil { + return err + } + defer res.Body.Close() // always close body + + if isNotFound(res) { + return errors.New("not found") + } + + err = json.NewDecoder(res.Body).Decode(&rec) + if err != nil { + return err + } + + return nil +} + +func isNotFound(res *http.Response) bool { + return res.StatusCode == http.StatusNotFound +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/common.go b/vendor/github.com/michaelklishin/rabbit-hole/common.go new file mode 100644 index 000000000..f633f0b5f --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/common.go @@ -0,0 +1,57 @@ +package rabbithole + +import "strconv" + +// Extra arguments as a map (on queues, bindings, etc) +type Properties map[string]interface{} + +// Port used by RabbitMQ or clients +type Port int + +func (p *Port) UnmarshalJSON(b []byte) error { + stringValue := string(b) + var parsed int64 + var err error + if stringValue[0] == '"' && stringValue[len(stringValue)-1] == '"' { + parsed, err = strconv.ParseInt(stringValue[1:len(stringValue)-1], 10, 32) + } else { + parsed, err = strconv.ParseInt(stringValue, 10, 32) + } + if err == nil { + *p = Port(int(parsed)) + } + return err +} + +// Rate of change of a numerical value +type RateDetails struct { + Rate float32 `json:"rate"` +} + +// RabbitMQ context (Erlang app) running on +// a node +type BrokerContext struct { + Node string `json:"node"` + Description string `json:"description"` + Path string `json:"path"` + Port Port `json:"port"` + Ignore bool `json:"ignore_in_use"` +} + +// Basic published messages statistics +type MessageStats struct { + Publish int `json:"publish"` + PublishDetails RateDetails `json:"publish_details"` + Deliver int `json:"deliver"` + DeliverDetails RateDetails `json:"deliver_details"` + DeliverNoAck int `json:"deliver_noack"` + DeliverNoAckDetails RateDetails `json:"deliver_noack_details"` + DeliverGet int `json:"deliver_get"` + DeliverGetDetails RateDetails `json:"deliver_get_details"` + Redeliver int `json:"redeliver"` + RedeliverDetails RateDetails `json:"redeliver_details"` + Get int `json:"get"` + GetDetails RateDetails `json:"get_details"` + GetNoAck int `json:"get_no_ack"` + GetNoAckDetails RateDetails `json:"get_no_ack_details"` +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/connections.go b/vendor/github.com/michaelklishin/rabbit-hole/connections.go new file mode 100644 index 000000000..bee859544 --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/connections.go @@ -0,0 +1,131 @@ +package rabbithole + +import ( + "net/http" + "net/url" +) + +// Provides information about connection to a RabbitMQ node. +type ConnectionInfo struct { + // Connection name + Name string `json:"name"` + // Node the client is connected to + Node string `json:"node"` + // Number of open channels + Channels int `json:"channels"` + // Connection state + State string `json:"state"` + // Connection type, network (via AMQP client) or direct (via direct Erlang client) + Type string `json:"type"` + + // Server port + Port Port `json:"port"` + // Client port + PeerPort Port `json:"peer_port"` + + // Server host + Host string `json:"host"` + // Client host + PeerHost string `json:"peer_host"` + + // Last connection blocking reason, if any + LastBlockedBy string `json:"last_blocked_by"` + // When connection was last blocked + LastBlockedAge string `json:"last_blocked_age"` + + // True if connection uses TLS/SSL + UsesTLS bool `json:"ssl"` + // Client certificate subject + PeerCertSubject string `json:"peer_cert_subject"` + // Client certificate validity + PeerCertValidity string `json:"peer_cert_validity"` + // Client certificate issuer + PeerCertIssuer string `json:"peer_cert_issuer"` + + // TLS/SSL protocol and version + SSLProtocol string `json:"ssl_protocol"` + // Key exchange mechanism + SSLKeyExchange string `json:"ssl_key_exchange"` + // SSL cipher suite used + SSLCipher string `json:"ssl_cipher"` + // SSL hash + SSLHash string `json:"ssl_hash"` + + // Protocol, e.g. AMQP 0-9-1 or MQTT 3-1 + Protocol string `json:"protocol"` + User string `json:"user"` + // Virtual host + Vhost string `json:"vhost"` + + // Heartbeat timeout + Timeout int `json:"timeout"` + // Maximum frame size (AMQP 0-9-1) + FrameMax int `json:"frame_max"` + + // A map of client properties (name, version, capabilities, etc) + ClientProperties Properties `json:"client_properties"` + + // Octets received + RecvOct uint64 `json:"recv_oct"` + // Octets sent + SendOct uint64 `json:"send_oct"` + RecvCount uint64 `json:"recv_cnt"` + SendCount uint64 `json:"send_cnt"` + SendPending uint64 `json:"send_pend"` + // Ingress data rate + RecvOctDetails RateDetails `json:"recv_oct_details"` + // Egress data rate + SendOctDetails RateDetails `json:"send_oct_details"` +} + +// +// GET /api/connections +// + +func (c *Client) ListConnections() (rec []ConnectionInfo, err error) { + req, err := newGETRequest(c, "connections") + if err != nil { + return []ConnectionInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []ConnectionInfo{}, err + } + + return rec, nil +} + +// +// GET /api/connections/{name} +// + +func (c *Client) GetConnection(name string) (rec *ConnectionInfo, err error) { + req, err := newGETRequest(c, "connections/"+url.QueryEscape(name)) + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// DELETE /api/connections/{name} +// + +func (c *Client) CloseConnection(name string) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "connections/"+url.QueryEscape(name), nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/doc.go b/vendor/github.com/michaelklishin/rabbit-hole/doc.go new file mode 100644 index 000000000..549be7017 --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/doc.go @@ -0,0 +1,177 @@ +/* +Rabbit Hole is a Go client for the RabbitMQ HTTP API. + +All HTTP API operations are accessible via `rabbithole.Client`, which +should be instantiated with `rabbithole.NewClient`. + + // URI, username, password + rmqc, _ = NewClient("http://127.0.0.1:15672", "guest", "guest") + +Getting Overview + + res, err := rmqc.Overview() + +Node and Cluster Status + + var err error + + // => []NodeInfo, err + xs, err := rmqc.ListNodes() + + node, err := rmqc.GetNode("rabbit@mercurio") + // => NodeInfo, err + +Operations on Connections + + xs, err := rmqc.ListConnections() + // => []ConnectionInfo, err + + conn, err := rmqc.GetConnection("127.0.0.1:50545 -> 127.0.0.1:5672") + // => ConnectionInfo, err + + // Forcefully close connection + _, err := rmqc.CloseConnection("127.0.0.1:50545 -> 127.0.0.1:5672") + // => *http.Response, err + +Operations on Channels + + xs, err := rmqc.ListChannels() + // => []ChannelInfo, err + + ch, err := rmqc.GetChannel("127.0.0.1:50545 -> 127.0.0.1:5672 (1)") + // => ChannelInfo, err + +Operations on Exchanges + + xs, err := rmqc.ListExchanges() + // => []ExchangeInfo, err + + // list exchanges in a vhost + xs, err := rmqc.ListExchangesIn("/") + // => []ExchangeInfo, err + + // information about individual exchange + x, err := rmqc.GetExchange("/", "amq.fanout") + // => ExchangeInfo, err + + // declares an exchange + resp, err := rmqc.DeclareExchange("/", "an.exchange", ExchangeSettings{Type: "fanout", Durable: false}) + // => *http.Response, err + + // deletes individual exchange + resp, err := rmqc.DeleteExchange("/", "an.exchange") + // => *http.Response, err + +Operations on Queues + + xs, err := rmqc.ListQueues() + // => []QueueInfo, err + + // list queues in a vhost + xs, err := rmqc.ListQueuesIn("/") + // => []QueueInfo, err + + // information about individual queue + x, err := rmqc.GetQueue("/", "a.queue") + // => QueueInfo, err + + // declares a queue + resp, err := rmqc.DeclareQueue("/", "a.queue", QueueSettings{Durable: false}) + // => *http.Response, err + + // deletes individual queue + resp, err := rmqc.DeleteQueue("/", "a.queue") + // => *http.Response, err + + // purges all messages in queue + resp, err := rmqc.PurgeQueue("/", "a.queue") + // => *http.Response, err + +Operations on Bindings + + bs, err := rmqc.ListBindings() + // => []BindingInfo, err + + // list bindings in a vhost + bs, err := rmqc.ListBindingsIn("/") + // => []BindingInfo, err + + // list bindings of a queue + bs, err := rmqc.ListQueueBindings("/", "a.queue") + // => []BindingInfo, err + + // declare a binding + resp, err := rmqc.DeclareBinding("/", BindingInfo{ + Source: "an.exchange", + Destination: "a.queue", + DestinationType: "queue", + RoutingKey: "#", + }) + // => *http.Response, err + + // deletes individual binding + resp, err := rmqc.DeleteBinding("/", BindingInfo{ + Source: "an.exchange", + Destination: "a.queue", + DestinationType: "queue", + RoutingKey: "#", + PropertiesKey: "%23", + }) + // => *http.Response, err + +Operations on Vhosts + + xs, err := rmqc.ListVhosts() + // => []VhostInfo, err + + // information about individual vhost + x, err := rmqc.GetVhost("/") + // => VhostInfo, err + + // creates or updates individual vhost + resp, err := rmqc.PutVhost("/", VhostSettings{Tracing: false}) + // => *http.Response, err + + // deletes individual vhost + resp, err := rmqc.DeleteVhost("/") + // => *http.Response, err + +Managing Users + + xs, err := rmqc.ListUsers() + // => []UserInfo, err + + // information about individual user + x, err := rmqc.GetUser("my.user") + // => UserInfo, err + + // creates or updates individual user + resp, err := rmqc.PutUser("my.user", UserSettings{Password: "s3krE7", Tags: "management policymaker"}) + // => *http.Response, err + + // deletes individual user + resp, err := rmqc.DeleteUser("my.user") + // => *http.Response, err + +Managing Permissions + + xs, err := rmqc.ListPermissions() + // => []PermissionInfo, err + + // permissions of individual user + x, err := rmqc.ListPermissionsOf("my.user") + // => []PermissionInfo, err + + // permissions of individual user in vhost + x, err := rmqc.GetPermissionsIn("/", "my.user") + // => PermissionInfo, err + + // updates permissions of user in vhost + resp, err := rmqc.UpdatePermissionsIn("/", "my.user", Permissions{Configure: ".*", Write: ".*", Read: ".*"}) + // => *http.Response, err + + // revokes permissions in vhost + resp, err := rmqc.ClearPermissionsIn("/", "my.user") + // => *http.Response, err +*/ +package rabbithole diff --git a/vendor/github.com/michaelklishin/rabbit-hole/exchanges.go b/vendor/github.com/michaelklishin/rabbit-hole/exchanges.go new file mode 100644 index 000000000..7a78df55b --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/exchanges.go @@ -0,0 +1,219 @@ +package rabbithole + +import ( + "encoding/json" + "net/http" + "net/url" +) + +// +// GET /api/exchanges +// + +type IngressEgressStats struct { + PublishIn int `json:"publish_in"` + PublishInDetails RateDetails `json:"publish_in_details"` + + PublishOut int `json:"publish_out"` + PublishOutDetails RateDetails `json:"publish_out_details"` +} + +type ExchangeInfo struct { + Name string `json:"name"` + Vhost string `json:"vhost"` + Type string `json:"type"` + Durable bool `json:"durable"` + AutoDelete bool `json:"auto_delete"` + Internal bool `json:"internal"` + Arguments map[string]interface{} `json:"arguments"` + + MessageStats IngressEgressStats `json:"message_stats"` +} + +type ExchangeSettings struct { + Type string `json:"type"` + Durable bool `json:"durable"` + AutoDelete bool `json:"auto_delete"` + Arguments map[string]interface{} `json:"arguments"` +} + +func (c *Client) ListExchanges() (rec []ExchangeInfo, err error) { + req, err := newGETRequest(c, "exchanges") + if err != nil { + return []ExchangeInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []ExchangeInfo{}, err + } + + return rec, nil +} + +// +// GET /api/exchanges/{vhost} +// + +func (c *Client) ListExchangesIn(vhost string) (rec []ExchangeInfo, err error) { + req, err := newGETRequest(c, "exchanges/"+url.QueryEscape(vhost)) + if err != nil { + return []ExchangeInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []ExchangeInfo{}, err + } + + return rec, nil +} + +// +// GET /api/exchanges/{vhost}/{name} +// + +// Example response: +// +// { +// "incoming": [ +// { +// "stats": { +// "publish": 2760, +// "publish_details": { +// "rate": 20 +// } +// }, +// "channel_details": { +// "name": "127.0.0.1:46928 -> 127.0.0.1:5672 (2)", +// "number": 2, +// "connection_name": "127.0.0.1:46928 -> 127.0.0.1:5672", +// "peer_port": 46928, +// "peer_host": "127.0.0.1" +// } +// } +// ], +// "outgoing": [ +// { +// "stats": { +// "publish": 1280, +// "publish_details": { +// "rate": 20 +// } +// }, +// "queue": { +// "name": "amq.gen-7NhO_yRr4lDdp-8hdnvfuw", +// "vhost": "rabbit\/hole" +// } +// } +// ], +// "message_stats": { +// "publish_in": 2760, +// "publish_in_details": { +// "rate": 20 +// }, +// "publish_out": 1280, +// "publish_out_details": { +// "rate": 20 +// } +// }, +// "name": "amq.fanout", +// "vhost": "rabbit\/hole", +// "type": "fanout", +// "durable": true, +// "auto_delete": false, +// "internal": false, +// "arguments": { +// } +// } + +type ExchangeIngressDetails struct { + Stats MessageStats `json:"stats"` + ChannelDetails PublishingChannel `json:"channel_details"` +} + +type PublishingChannel struct { + Number int `json:"number"` + Name string `json:"name"` + ConnectionName string `json:"connection_name"` + PeerPort Port `json:"peer_port"` + PeerHost string `json:"peer_host"` +} + +type NameAndVhost struct { + Name string `json:"name"` + Vhost string `json:"vhost"` +} + +type ExchangeEgressDetails struct { + Stats MessageStats `json:"stats"` + Queue NameAndVhost `json:"queue"` +} + +type DetailedExchangeInfo struct { + Name string `json:"name"` + Vhost string `json:"vhost"` + Type string `json:"type"` + Durable bool `json:"durable"` + AutoDelete bool `json:"auto_delete"` + Internal bool `json:"internal"` + Arguments map[string]interface{} `json:"arguments"` + + Incoming []ExchangeIngressDetails `json:"incoming"` + Outgoing []ExchangeEgressDetails `json:"outgoing"` +} + +func (c *Client) GetExchange(vhost, exchange string) (rec *DetailedExchangeInfo, err error) { + req, err := newGETRequest(c, "exchanges/"+url.QueryEscape(vhost)+"/"+exchange) + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// PUT /api/exchanges/{vhost}/{exchange} +// + +func (c *Client) DeclareExchange(vhost, exchange string, info ExchangeSettings) (res *http.Response, err error) { + if info.Arguments == nil { + info.Arguments = make(map[string]interface{}) + } + body, err := json.Marshal(info) + if err != nil { + return nil, err + } + + req, err := newRequestWithBody(c, "PUT", "exchanges/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(exchange), body) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// +// DELETE /api/exchanges/{vhost}/{name} +// + +func (c *Client) DeleteExchange(vhost, exchange string) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "exchanges/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(exchange), nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/federation.go b/vendor/github.com/michaelklishin/rabbit-hole/federation.go new file mode 100644 index 000000000..23f37b79d --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/federation.go @@ -0,0 +1,72 @@ +package rabbithole + +import ( + "encoding/json" + "net/http" + "net/url" +) + +// Federation definition: additional arguments +// added to the entities (queues, exchanges or both) +// that match a policy. +type FederationDefinition struct { + Uri string `json:"uri"` + Expires int `json:"expires"` + MessageTTL int32 `json:"message-ttl"` + MaxHops int `json:"max-hops"` + PrefetchCount int `json:"prefetch-count"` + ReconnectDelay int `json:"reconnect-delay"` + AckMode string `json:"ack-mode"` + TrustUserId bool `json:"trust-user-id"` +} + +// Represents a configured Federation upstream. +type FederationUpstream struct { + Definition FederationDefinition `json:"value"` +} + +// +// PUT /api/parameters/federation-upstream/{vhost}/{upstream} +// + +// Updates a federation upstream +func (c *Client) PutFederationUpstream(vhost string, upstreamName string, fDef FederationDefinition) (res *http.Response, err error) { + fedUp := FederationUpstream{ + Definition: fDef, + } + body, err := json.Marshal(fedUp) + if err != nil { + return nil, err + } + + req, err := newRequestWithBody(c, "PUT", "parameters/federation-upstream/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(upstreamName), body) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// +// DELETE /api/parameters/federation-upstream/{vhost}/{name} +// + +// Deletes a federation upstream. +func (c *Client) DeleteFederationUpstream(vhost, upstreamName string) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "parameters/federation-upstream/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(upstreamName), nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/misc.go b/vendor/github.com/michaelklishin/rabbit-hole/misc.go new file mode 100644 index 000000000..9fbd9887c --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/misc.go @@ -0,0 +1,83 @@ +package rabbithole + +// +// GET /api/overview +// + +type QueueTotals struct { + Messages int `json:"messages"` + MessagesDetails RateDetails `json:"messages_details"` + + MessagesReady int `json:"messages_ready"` + MessagesReadyDetails RateDetails `json:"messages_ready_details"` + + MessagesUnacknowledged int `json:"messages_unacknowledged"` + MessagesUnacknowledgedDetails RateDetails `json:"messages_unacknowledged_details"` +} + +type ObjectTotals struct { + Consumers int `json:"consumers"` + Queues int `json:"queues"` + Exchanges int `json:"exchanges"` + Connections int `json:"connections"` + Channels int `json:"channels"` +} + +type Listener struct { + Node string `json:"node"` + Protocol string `json:"protocol"` + IpAddress string `json:"ip_address"` + Port Port `json:"port"` +} + +type Overview struct { + ManagementVersion string `json:"management_version"` + StatisticsLevel string `json:"statistics_level"` + RabbitMQVersion string `json:"rabbitmq_version"` + ErlangVersion string `json:"erlang_version"` + FullErlangVersion string `json:"erlang_full_version"` + ExchangeTypes []ExchangeType `json:"exchange_types"` + MessageStats MessageStats `json:"message_stats"` + QueueTotals QueueTotals `json:"queue_totals"` + ObjectTotals ObjectTotals `json:"object_totals"` + Node string `json:"node"` + StatisticsDBNode string `json:"statistics_db_node"` + Listeners []Listener `json:"listeners"` + Contexts []BrokerContext `json:"contexts"` +} + +func (c *Client) Overview() (rec *Overview, err error) { + req, err := newGETRequest(c, "overview") + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// GET /api/whoami +// + +type WhoamiInfo struct { + Name string `json:"name"` + Tags string `json:"tags"` + AuthBackend string `json:"auth_backend"` +} + +func (c *Client) Whoami() (rec *WhoamiInfo, err error) { + req, err := newGETRequest(c, "whoami") + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/nodes.go b/vendor/github.com/michaelklishin/rabbit-hole/nodes.go new file mode 100644 index 000000000..38922cb3f --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/nodes.go @@ -0,0 +1,301 @@ +package rabbithole + +import ( + "net/url" +) + +// TODO: this probably should be fixed in RabbitMQ management plugin +type OsPid string + +type NameDescriptionEnabled struct { + Name string `json:"name"` + Description string `json:"description"` + Enabled bool `json:"enabled"` +} + +type AuthMechanism NameDescriptionEnabled + +type ExchangeType NameDescriptionEnabled + +type NameDescriptionVersion struct { + Name string `json:"name"` + Description string `json:"description"` + Version string `json:"version"` +} + +type ErlangApp NameDescriptionVersion + +type NodeInfo struct { + Name string `json:"name"` + NodeType string `json:"type"` + IsRunning bool `json:"running"` + OsPid OsPid `json:"os_pid"` + + FdUsed int `json:"fd_used"` + FdTotal int `json:"fd_total"` + SocketsUsed int `json:"sockets_used"` + SocketsTotal int `json:"sockets_total"` + MemUsed int `json:"mem_used"` + MemLimit int `json:"mem_limit"` + MemAlarm bool `json:"mem_alarm"` + DiskFree int `json:"disk_free"` + DiskFreeLimit int `json:"disk_free_limit"` + DiskFreeAlarm bool `json:"disk_free_alarm"` + + // Erlang scheduler run queue length + RunQueueLength uint32 `json:"run_queue"` + Processors uint32 `json:"processors"` + Uptime uint64 `json:"uptime"` + + ExchangeTypes []ExchangeType `json:"exchange_types"` + AuthMechanisms []AuthMechanism `json:"auth_mechanisms"` + ErlangApps []ErlangApp `json:"applications"` + Contexts []BrokerContext `json:"contexts"` +} + +// +// GET /api/nodes +// + +func (c *Client) ListNodes() (rec []NodeInfo, err error) { + req, err := newGETRequest(c, "nodes") + if err != nil { + return []NodeInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// GET /api/nodes/{name} +// + +// { +// "partitions": [], +// "os_pid": "39292", +// "fd_used": 35, +// "fd_total": 256, +// "sockets_used": 4, +// "sockets_total": 138, +// "mem_used": 69964432, +// "mem_limit": 2960660889, +// "mem_alarm": false, +// "disk_free_limit": 50000000, +// "disk_free": 188362731520, +// "disk_free_alarm": false, +// "proc_used": 370, +// "proc_total": 1048576, +// "statistics_level": "fine", +// "uptime": 98355255, +// "run_queue": 0, +// "processors": 8, +// "exchange_types": [ +// { +// "name": "topic", +// "description": "AMQP topic exchange, as per the AMQP specification", +// "enabled": true +// }, +// { +// "name": "x-consistent-hash", +// "description": "Consistent Hashing Exchange", +// "enabled": true +// }, +// { +// "name": "fanout", +// "description": "AMQP fanout exchange, as per the AMQP specification", +// "enabled": true +// }, +// { +// "name": "direct", +// "description": "AMQP direct exchange, as per the AMQP specification", +// "enabled": true +// }, +// { +// "name": "headers", +// "description": "AMQP headers exchange, as per the AMQP specification", +// "enabled": true +// } +// ], +// "auth_mechanisms": [ +// { +// "name": "AMQPLAIN", +// "description": "QPid AMQPLAIN mechanism", +// "enabled": true +// }, +// { +// "name": "PLAIN", +// "description": "SASL PLAIN authentication mechanism", +// "enabled": true +// }, +// { +// "name": "RABBIT-CR-DEMO", +// "description": "RabbitMQ Demo challenge-response authentication mechanism", +// "enabled": false +// } +// ], +// "applications": [ +// { +// "name": "amqp_client", +// "description": "RabbitMQ AMQP Client", +// "version": "3.2.0" +// }, +// { +// "name": "asn1", +// "description": "The Erlang ASN1 compiler version 2.0.3", +// "version": "2.0.3" +// }, +// { +// "name": "cowboy", +// "description": "Small, fast, modular HTTP server.", +// "version": "0.5.0-rmq3.2.0-git4b93c2d" +// }, +// { +// "name": "crypto", +// "description": "CRYPTO version 2", +// "version": "3.1" +// }, +// { +// "name": "inets", +// "description": "INETS CXC 138 49", +// "version": "5.9.6" +// }, +// { +// "name": "kernel", +// "description": "ERTS CXC 138 10", +// "version": "2.16.3" +// }, +// { +// "name": "mnesia", +// "description": "MNESIA CXC 138 12", +// "version": "4.10" +// }, +// { +// "name": "mochiweb", +// "description": "MochiMedia Web Server", +// "version": "2.7.0-rmq3.2.0-git680dba8" +// }, +// { +// "name": "os_mon", +// "description": "CPO CXC 138 46", +// "version": "2.2.13" +// }, +// { +// "name": "public_key", +// "description": "Public key infrastructure", +// "version": "0.20" +// }, +// { +// "name": "rabbit", +// "description": "RabbitMQ", +// "version": "3.2.0" +// }, +// { +// "name": "rabbitmq_consistent_hash_exchange", +// "description": "Consistent Hash Exchange Type", +// "version": "3.2.0" +// }, +// { +// "name": "rabbitmq_management", +// "description": "RabbitMQ Management Console", +// "version": "3.2.0" +// }, +// { +// "name": "rabbitmq_management_agent", +// "description": "RabbitMQ Management Agent", +// "version": "3.2.0" +// }, +// { +// "name": "rabbitmq_mqtt", +// "description": "RabbitMQ MQTT Adapter", +// "version": "3.2.0" +// }, +// { +// "name": "rabbitmq_shovel", +// "description": "Data Shovel for RabbitMQ", +// "version": "3.2.0" +// }, +// { +// "name": "rabbitmq_shovel_management", +// "description": "Shovel Status", +// "version": "3.2.0" +// }, +// { +// "name": "rabbitmq_stomp", +// "description": "Embedded Rabbit Stomp Adapter", +// "version": "3.2.0" +// }, +// { +// "name": "rabbitmq_web_dispatch", +// "description": "RabbitMQ Web Dispatcher", +// "version": "3.2.0" +// }, +// { +// "name": "rabbitmq_web_stomp", +// "description": "Rabbit WEB-STOMP - WebSockets to Stomp adapter", +// "version": "3.2.0" +// }, +// { +// "name": "sasl", +// "description": "SASL CXC 138 11", +// "version": "2.3.3" +// }, +// { +// "name": "sockjs", +// "description": "SockJS", +// "version": "0.3.4-rmq3.2.0-git3132eb9" +// }, +// { +// "name": "ssl", +// "description": "Erlang\/OTP SSL application", +// "version": "5.3.1" +// }, +// { +// "name": "stdlib", +// "description": "ERTS CXC 138 10", +// "version": "1.19.3" +// }, +// { +// "name": "webmachine", +// "description": "webmachine", +// "version": "1.10.3-rmq3.2.0-gite9359c7" +// }, +// { +// "name": "xmerl", +// "description": "XML parser", +// "version": "1.3.4" +// } +// ], +// "contexts": [ +// { +// "description": "Redirect to port 15672", +// "path": "\/", +// "port": 55672, +// "ignore_in_use": true +// }, +// { +// "description": "RabbitMQ Management", +// "path": "\/", +// "port": 15672 +// } +// ], +// "name": "rabbit@mercurio", +// "type": "disc", +// "running": true +// } + +func (c *Client) GetNode(name string) (rec *NodeInfo, err error) { + req, err := newGETRequest(c, "nodes/"+url.QueryEscape(name)) + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/permissions.go b/vendor/github.com/michaelklishin/rabbit-hole/permissions.go new file mode 100644 index 000000000..677835000 --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/permissions.go @@ -0,0 +1,126 @@ +package rabbithole + +import ( + "encoding/json" + "net/http" + "net/url" +) + +// +// GET /api/permissions +// + +// Example response: +// +// [{"user":"guest","vhost":"/","configure":".*","write":".*","read":".*"}] + +type PermissionInfo struct { + User string `json:"user"` + Vhost string `json:"vhost"` + + // Configuration permissions + Configure string `json:"configure"` + // Write permissions + Write string `json:"write"` + // Read permissions + Read string `json:"read"` +} + +// Returns permissions for all users and virtual hosts. +func (c *Client) ListPermissions() (rec []PermissionInfo, err error) { + req, err := newGETRequest(c, "permissions/") + if err != nil { + return []PermissionInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []PermissionInfo{}, err + } + + return rec, nil +} + +// +// GET /api/users/{user}/permissions +// + +// Returns permissions of a specific user. +func (c *Client) ListPermissionsOf(username string) (rec []PermissionInfo, err error) { + req, err := newGETRequest(c, "users/"+url.QueryEscape(username)+"/permissions") + if err != nil { + return []PermissionInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []PermissionInfo{}, err + } + + return rec, nil +} + +// +// GET /api/permissions/{vhost}/{user} +// + +// Returns permissions of user in virtual host. +func (c *Client) GetPermissionsIn(vhost, username string) (rec PermissionInfo, err error) { + req, err := newGETRequest(c, "permissions/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(username)) + if err != nil { + return PermissionInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return PermissionInfo{}, err + } + + return rec, nil +} + +// +// PUT /api/permissions/{vhost}/{user} +// + +type Permissions struct { + Configure string `json:"configure"` + Write string `json:"write"` + Read string `json:"read"` +} + +// Updates permissions of user in virtual host. +func (c *Client) UpdatePermissionsIn(vhost, username string, permissions Permissions) (res *http.Response, err error) { + body, err := json.Marshal(permissions) + if err != nil { + return nil, err + } + + req, err := newRequestWithBody(c, "PUT", "permissions/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(username), body) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// +// DELETE /api/permissions/{vhost}/{user} +// + +// Clears (deletes) permissions of user in virtual host. +func (c *Client) ClearPermissionsIn(vhost, username string) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "permissions/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(username), nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/plugins.go b/vendor/github.com/michaelklishin/rabbit-hole/plugins.go new file mode 100644 index 000000000..b2b9b266b --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/plugins.go @@ -0,0 +1,31 @@ +package rabbithole + +func (c *Client) EnabledProtocols() (xs []string, err error) { + overview, err := c.Overview() + if err != nil { + return []string{}, err + } + + // we really need to implement Map/Filter/etc. MK. + xs = make([]string, len(overview.Listeners)) + for i, lnr := range overview.Listeners { + xs[i] = lnr.Protocol + } + + return xs, nil +} + +func (c *Client) ProtocolPorts() (res map[string]Port, err error) { + res = map[string]Port{} + + overview, err := c.Overview() + if err != nil { + return res, err + } + + for _, lnr := range overview.Listeners { + res[lnr.Protocol] = lnr.Port + } + + return res, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/policies.go b/vendor/github.com/michaelklishin/rabbit-hole/policies.go new file mode 100644 index 000000000..d323c564b --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/policies.go @@ -0,0 +1,127 @@ +package rabbithole + +import ( + "encoding/json" + "net/http" + "net/url" +) + +// Policy definition: additional arguments +// added to the entities (queues, exchanges or both) +// that match a policy. +type PolicyDefinition map[string]interface{} + +type NodeNames []string + +// Represents a configured policy. +type Policy struct { + // Virtual host this policy is in. + Vhost string `json:"vhost"` + // Regular expression pattern used to match queues and exchanges, + // , e.g. "^ha\..+" + Pattern string `json:"pattern"` + // What this policy applies to: "queues", "exchanges", etc. + ApplyTo string `json:"apply-to"` + Name string `json:"name"` + Priority int `json:"priority"` + // Additional arguments added to the entities (queues, + // exchanges or both) that match a policy + Definition PolicyDefinition `json:"definition"` +} + +// +// GET /api/policies +// + +// Return all policies (across all virtual hosts). +func (c *Client) ListPolicies() (rec []Policy, err error) { + req, err := newGETRequest(c, "policies") + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// GET /api/policies/{vhost} +// + +// Returns policies in a specific virtual host. +func (c *Client) ListPoliciesIn(vhost string) (rec []Policy, err error) { + req, err := newGETRequest(c, "policies/"+url.QueryEscape(vhost)) + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// GET /api/policies/{vhost}/{name} +// + +// Returns individual policy in virtual host. +func (c *Client) GetPolicy(vhost, name string) (rec *Policy, err error) { + req, err := newGETRequest(c, "policies/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(name)) + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// PUT /api/policies/{vhost}/{name} +// + +// Updates a policy. +func (c *Client) PutPolicy(vhost string, name string, policy Policy) (res *http.Response, err error) { + body, err := json.Marshal(policy) + if err != nil { + return nil, err + } + + req, err := newRequestWithBody(c, "PUT", "policies/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(name), body) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// +// DELETE /api/policies/{vhost}/{name} +// + +// Deletes a policy. +func (c *Client) DeletePolicy(vhost, name string) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "policies/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(name), nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/queues.go b/vendor/github.com/michaelklishin/rabbit-hole/queues.go new file mode 100644 index 000000000..85919ed3c --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/queues.go @@ -0,0 +1,283 @@ +package rabbithole + +import ( + "encoding/json" + "net/http" + "net/url" +) + +// Information about backing queue (queue storage engine). +type BackingQueueStatus struct { + Q1 int `json:"q1"` + Q2 int `json:"q2"` + Q3 int `json:"q3"` + Q4 int `json:"q4"` + // Total queue length + Length int64 `json:"len"` + // Number of pending acks from consumers + PendingAcks int64 `json:"pending_acks"` + // Number of messages held in RAM + RAMMessageCount int64 `json:"ram_msg_count"` + // Number of outstanding acks held in RAM + RAMAckCount int64 `json:"ram_ack_count"` + // Number of persistent messages in the store + PersistentCount int64 `json:"persistent_count"` + // Average ingress (inbound) rate, not including messages + // that straight through to auto-acking consumers. + AverageIngressRate float64 `json:"avg_ingress_rate"` + // Average egress (outbound) rate, not including messages + // that straight through to auto-acking consumers. + AverageEgressRate float64 `json:"avg_egress_rate"` + // rate at which unacknowledged message records enter RAM, + // e.g. because messages are delivered requiring acknowledgement + AverageAckIngressRate float32 `json:"avg_ack_ingress_rate"` + // rate at which unacknowledged message records leave RAM, + // e.g. because acks arrive or unacked messages are paged out + AverageAckEgressRate float32 `json:"avg_ack_egress_rate"` +} + +type OwnerPidDetails struct { + Name string `json:"name"` + PeerPort Port `json:"peer_port"` + PeerHost string `json:"peer_host"` +} + +type QueueInfo struct { + // Queue name + Name string `json:"name"` + // Virtual host this queue belongs to + Vhost string `json:"vhost"` + // Is this queue durable? + Durable bool `json:"durable"` + // Is this queue auto-delted? + AutoDelete bool `json:"auto_delete"` + // Extra queue arguments + Arguments map[string]interface{} `json:"arguments"` + + // RabbitMQ node that hosts master for this queue + Node string `json:"node"` + // Queue status + Status string `json:"status"` + + // Total amount of RAM used by this queue + Memory int64 `json:"memory"` + // How many consumers this queue has + Consumers int `json:"consumers"` + // Utilisation of all the consumers + ConsumerUtilisation float64 `json:"consumer_utilisation"` + // If there is an exclusive consumer, its consumer tag + ExclusiveConsumerTag string `json:"exclusive_consumer_tag"` + + // Policy applied to this queue, if any + Policy string `json:"policy"` + + // Total bytes of messages in this queues + MessagesBytes int64 `json:"message_bytes"` + MessagesBytesPersistent int64 `json:"message_bytes_persistent"` + MessagesBytesRAM int64 `json:"message_bytes_ram"` + + // Total number of messages in this queue + Messages int `json:"messages"` + MessagesDetails RateDetails `json:"messages_details"` + MessagesPersistent int `json:"messages_persistent"` + MessagesRAM int `json:"messages_ram"` + + // Number of messages ready to be delivered + MessagesReady int `json:"messages_ready"` + MessagesReadyDetails RateDetails `json:"messages_ready_details"` + + // Number of messages delivered and pending acknowledgements from consumers + MessagesUnacknowledged int `json:"messages_unacknowledged"` + MessagesUnacknowledgedDetails RateDetails `json:"messages_unacknowledged_details"` + + MessageStats MessageStats `json:"message_stats"` + + OwnerPidDetails OwnerPidDetails `json:"owner_pid_details"` + + BackingQueueStatus BackingQueueStatus `json:"backing_queue_status"` +} + +type DetailedQueueInfo QueueInfo + +// +// GET /api/queues +// + +// [ +// { +// "owner_pid_details": { +// "name": "127.0.0.1:46928 -> 127.0.0.1:5672", +// "peer_port": 46928, +// "peer_host": "127.0.0.1" +// }, +// "message_stats": { +// "publish": 19830, +// "publish_details": { +// "rate": 5 +// } +// }, +// "messages": 15, +// "messages_details": { +// "rate": 0 +// }, +// "messages_ready": 15, +// "messages_ready_details": { +// "rate": 0 +// }, +// "messages_unacknowledged": 0, +// "messages_unacknowledged_details": { +// "rate": 0 +// }, +// "policy": "", +// "exclusive_consumer_tag": "", +// "consumers": 0, +// "memory": 143112, +// "backing_queue_status": { +// "q1": 0, +// "q2": 0, +// "delta": [ +// "delta", +// "undefined", +// 0, +// "undefined" +// ], +// "q3": 0, +// "q4": 15, +// "len": 15, +// "pending_acks": 0, +// "target_ram_count": "infinity", +// "ram_msg_count": 15, +// "ram_ack_count": 0, +// "next_seq_id": 19830, +// "persistent_count": 0, +// "avg_ingress_rate": 4.9920127795527, +// "avg_egress_rate": 4.9920127795527, +// "avg_ack_ingress_rate": 0, +// "avg_ack_egress_rate": 0 +// }, +// "status": "running", +// "name": "amq.gen-QLEaT5Rn_ogbN3O8ZOQt3Q", +// "vhost": "rabbit\/hole", +// "durable": false, +// "auto_delete": false, +// "arguments": { +// "x-message-ttl": 5000 +// }, +// "node": "rabbit@marzo" +// } +// ] + +func (c *Client) ListQueues() (rec []QueueInfo, err error) { + req, err := newGETRequest(c, "queues") + if err != nil { + return []QueueInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []QueueInfo{}, err + } + + return rec, nil +} + +// +// GET /api/queues/{vhost} +// + +func (c *Client) ListQueuesIn(vhost string) (rec []QueueInfo, err error) { + req, err := newGETRequest(c, "queues/"+url.QueryEscape(vhost)) + if err != nil { + return []QueueInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []QueueInfo{}, err + } + + return rec, nil +} + +// +// GET /api/queues/{vhost}/{name} +// + +func (c *Client) GetQueue(vhost, queue string) (rec *DetailedQueueInfo, err error) { + req, err := newGETRequest(c, "queues/"+url.QueryEscape(vhost)+"/"+queue) + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// PUT /api/exchanges/{vhost}/{exchange} +// + +type QueueSettings struct { + Durable bool `json:"durable"` + AutoDelete bool `json:"auto_delete"` + Arguments map[string]interface{} `json:"arguments"` +} + +func (c *Client) DeclareQueue(vhost, queue string, info QueueSettings) (res *http.Response, err error) { + if info.Arguments == nil { + info.Arguments = make(map[string]interface{}) + } + body, err := json.Marshal(info) + if err != nil { + return nil, err + } + + req, err := newRequestWithBody(c, "PUT", "queues/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(queue), body) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// +// DELETE /api/queues/{vhost}/{name} +// + +func (c *Client) DeleteQueue(vhost, queue string) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "queues/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(queue), nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// +// DELETE /api/queues/{vhost}/{name}/contents +// + +func (c *Client) PurgeQueue(vhost, queue string) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "queues/"+url.QueryEscape(vhost)+"/"+url.QueryEscape(queue)+"/contents", nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/users.go b/vendor/github.com/michaelklishin/rabbit-hole/users.go new file mode 100644 index 000000000..9f809697e --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/users.go @@ -0,0 +1,109 @@ +package rabbithole + +import ( + "encoding/json" + "net/http" + "net/url" +) + +type UserInfo struct { + Name string `json:"name"` + PasswordHash string `json:"password_hash"` + // Tags control permissions. Built-in tags: administrator, management, policymaker. + Tags string `json:"tags"` +} + +// Settings used to create users. Tags must be comma-separated. +type UserSettings struct { + Name string `json:"name"` + // Tags control permissions. Administrator grants full + // permissions, management grants management UI and HTTP API + // access, policymaker grants policy management permissions. + Tags string `json:"tags"` + + // *never* returned by RabbitMQ. Set by the client + // to create/update a user. MK. + Password string `json:"password"` +} + +// +// GET /api/users +// + +// Example response: +// [{"name":"guest","password_hash":"8LYTIFbVUwi8HuV/dGlp2BYsD1I=","tags":"administrator"}] + +// Returns a list of all users in a cluster. +func (c *Client) ListUsers() (rec []UserInfo, err error) { + req, err := newGETRequest(c, "users/") + if err != nil { + return []UserInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []UserInfo{}, err + } + + return rec, nil +} + +// +// GET /api/users/{name} +// + +// Returns information about individual user. +func (c *Client) GetUser(username string) (rec *UserInfo, err error) { + req, err := newGETRequest(c, "users/"+url.QueryEscape(username)) + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// PUT /api/users/{name} +// + +// Updates information about individual user. +func (c *Client) PutUser(username string, info UserSettings) (res *http.Response, err error) { + body, err := json.Marshal(info) + if err != nil { + return nil, err + } + + req, err := newRequestWithBody(c, "PUT", "users/"+url.QueryEscape(username), body) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// +// DELETE /api/users/{name} +// + +// Deletes user. +func (c *Client) DeleteUser(username string) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "users/"+url.QueryEscape(username), nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/github.com/michaelklishin/rabbit-hole/vhosts.go b/vendor/github.com/michaelklishin/rabbit-hole/vhosts.go new file mode 100644 index 000000000..d0f84b5f8 --- /dev/null +++ b/vendor/github.com/michaelklishin/rabbit-hole/vhosts.go @@ -0,0 +1,160 @@ +package rabbithole + +import ( + "encoding/json" + "net/http" + "net/url" +) + +// +// GET /api/vhosts +// + +// Example response: + +// [ +// { +// "message_stats": { +// "publish": 78, +// "publish_details": { +// "rate": 0 +// } +// }, +// "messages": 0, +// "messages_details": { +// "rate": 0 +// }, +// "messages_ready": 0, +// "messages_ready_details": { +// "rate": 0 +// }, +// "messages_unacknowledged": 0, +// "messages_unacknowledged_details": { +// "rate": 0 +// }, +// "recv_oct": 16653, +// "recv_oct_details": { +// "rate": 0 +// }, +// "send_oct": 40495, +// "send_oct_details": { +// "rate": 0 +// }, +// "name": "\/", +// "tracing": false +// }, +// { +// "name": "29dd51888b834698a8b5bc3e7f8623aa1c9671f5", +// "tracing": false +// } +// ] + +type VhostInfo struct { + // Virtual host name + Name string `json:"name"` + // True if tracing is enabled for this virtual host + Tracing bool `json:"tracing"` + + // Total number of messages in queues of this virtual host + Messages int `json:"messages"` + MessagesDetails RateDetails `json:"messages_details"` + + // Total number of messages ready to be delivered in queues of this virtual host + MessagesReady int `json:"messages_ready"` + MessagesReadyDetails RateDetails `json:"messages_ready_details"` + + // Total number of messages pending acknowledgement from consumers in this virtual host + MessagesUnacknowledged int `json:"messages_unacknowledged"` + MessagesUnacknowledgedDetails RateDetails `json:"messages_unacknowledged_details"` + + // Octets received + RecvOct uint64 `json:"recv_oct"` + // Octets sent + SendOct uint64 `json:"send_oct"` + RecvCount uint64 `json:"recv_cnt"` + SendCount uint64 `json:"send_cnt"` + SendPending uint64 `json:"send_pend"` + RecvOctDetails RateDetails `json:"recv_oct_details"` + SendOctDetails RateDetails `json:"send_oct_details"` +} + +// Returns a list of virtual hosts. +func (c *Client) ListVhosts() (rec []VhostInfo, err error) { + req, err := newGETRequest(c, "vhosts") + if err != nil { + return []VhostInfo{}, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return []VhostInfo{}, err + } + + return rec, nil +} + +// +// GET /api/vhosts/{name} +// + +// Returns information about a specific virtual host. +func (c *Client) GetVhost(vhostname string) (rec *VhostInfo, err error) { + req, err := newGETRequest(c, "vhosts/"+url.QueryEscape(vhostname)) + if err != nil { + return nil, err + } + + if err = executeAndParseRequest(c, req, &rec); err != nil { + return nil, err + } + + return rec, nil +} + +// +// PUT /api/vhosts/{name} +// + +// Settings used to create or modify virtual hosts. +type VhostSettings struct { + // True if tracing should be enabled. + Tracing bool `json:"tracing"` +} + +// Creates or updates a virtual host. +func (c *Client) PutVhost(vhostname string, settings VhostSettings) (res *http.Response, err error) { + body, err := json.Marshal(settings) + if err != nil { + return nil, err + } + + req, err := newRequestWithBody(c, "PUT", "vhosts/"+url.QueryEscape(vhostname), body) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// +// DELETE /api/vhosts/{name} +// + +// Deletes a virtual host. +func (c *Client) DeleteVhost(vhostname string) (res *http.Response, err error) { + req, err := newRequestWithBody(c, "DELETE", "vhosts/"+url.QueryEscape(vhostname), nil) + if err != nil { + return nil, err + } + + res, err = executeRequest(c, req) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 93971ec86..e4ea83897 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1448,6 +1448,12 @@ "path": "github.com/maximilien/softlayer-go/softlayer", "revision": "85659debe44fab5792fc92cf755c04b115b9dc19" }, + { + "checksumSHA1": "GSum+utW01N3KeMdvAPnsW0TemM=", + "path": "github.com/michaelklishin/rabbit-hole", + "revision": "88550829bcdcf614361c73459c903578eb44074e", + "revisionTime": "2016-07-06T11:10:56Z" + }, { "checksumSHA1": "7niW29CvYceZ6zbia6b/LT+yD/M=", "path": "github.com/mitchellh/cli", diff --git a/website/source/docs/providers/rabbitmq/index.html.markdown b/website/source/docs/providers/rabbitmq/index.html.markdown new file mode 100644 index 000000000..62c73cf00 --- /dev/null +++ b/website/source/docs/providers/rabbitmq/index.html.markdown @@ -0,0 +1,55 @@ +--- +layout: "rabbitmq" +page_title: "Provider: RabbitMQ" +sidebar_current: "docs-rabbitmq-index" +description: |- + A provider for a RabbitMQ Server. +--- + +# RabbitMQ Provider + +[RabbitMQ](http://www.rabbitmq.com) is an AMQP message broker server. The +RabbitMQ provider exposes resources used to manage the configuration of +resources in a RabbitMQ server. + +Use the navigation to the left to read about the available resources. + +## Example Usage + +The following is a minimal example: + +``` +# Configure the RabbitMQ provider +provider "rabbitmq" { + endpoint = "http://127.0.0.1" + username = "guest" + password = "guest" +} + +# Create a virtual host +resource "rabbitmq_vhost" "vhost_1" { + name = "vhost_1" +} +``` + +## Requirements + +The RabbitMQ management plugin must be enabled to use this provider. You can +enable the plugin by doing something similar to: + +``` +$ sudo rabbitmq-plugins enable rabbitmq_management +``` + +## Argument Reference + +The following arguments are supported: + +* `endpoint` - (Required) The HTTP URL of the management plugin on the + RabbitMQ server. The RabbitMQ management plugin *must* be enabled in order + to use this provder. _Note_: This is not the IP address or hostname of the + RabbitMQ server that you would use to access RabbitMQ directly. +* `username` - (Required) Username to use to authenticate with the server. +* `password` - (Optional) Password for the given user. +* `insecure` - (Optional) Trust self-signed certificates. +* `cacert_file` - (Optional) The path to a custom CA / intermediate certificate. diff --git a/website/source/docs/providers/rabbitmq/r/binding.html.markdown b/website/source/docs/providers/rabbitmq/r/binding.html.markdown new file mode 100644 index 000000000..0168e2055 --- /dev/null +++ b/website/source/docs/providers/rabbitmq/r/binding.html.markdown @@ -0,0 +1,89 @@ +--- +layout: "rabbitmq" +page_title: "RabbitMQ: rabbitmq_binding" +sidebar_current: "docs-rabbitmq-resource-binding" +description: |- + Creates and manages a binding on a RabbitMQ server. +--- + +# rabbitmq\_binding + +The ``rabbitmq_binding`` resource creates and manages a binding relationship +between a queue an exchange. + +## Example Usage + +``` +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" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `source` - (Required) The source exchange. + +* `vhost` - (Required) The vhost to create the resource in. + +* `destination` - (Required) The destination queue or exchange. + +* `destination_type` - (Required) The type of destination (queue or exchange). + +* `properties_key` - (Required) A unique key to refer to the binding. + +* `routing_key` - (Optional) A routing key for the binding. + +* `arguments` - (Optional) Additional key/value arguments for the binding. + +## Attributes Reference + +No further attributes are exported. + +## Import + +Bindings can be imported using the `id` which is composed of + `vhost/source/destination/destination_type/properties_key`. E.g. + +``` +terraform import rabbitmq_binding.test test/test/test/queue/%23 +``` diff --git a/website/source/docs/providers/rabbitmq/r/exchange.html.markdown b/website/source/docs/providers/rabbitmq/r/exchange.html.markdown new file mode 100644 index 000000000..62a83d257 --- /dev/null +++ b/website/source/docs/providers/rabbitmq/r/exchange.html.markdown @@ -0,0 +1,75 @@ +--- +layout: "rabbitmq" +page_title: "RabbitMQ: rabbitmq_exchange" +sidebar_current: "docs-rabbitmq-resource-exchange" +description: |- + Creates and manages an exchange on a RabbitMQ server. +--- + +# rabbitmq\_exchange + +The ``rabbitmq_exchange`` resource creates and manages an exchange. + +## Example Usage + +``` +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 + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the exchange. + +* `vhost` - (Required) The vhost to create the resource in. + +* `settings` - (Required) The settings of the exchange. The structure is + described below. + +The `settings` block supports: + +* `type` - (Required) The type of exchange. + +* `durable` - (Optional) Whether the exchange survives server restarts. + Defaults to `false`. + +* `auto_delete` - (Optional) Whether the exchange will self-delete when all + queues have finished using it. + +* `arguments` - (Optional) Additional key/value settings for the exchange. + +## Attributes Reference + +No further attributes are exported. + +## Import + +Exchanges can be imported using the `id` which is composed of `name@vhost`. +E.g. + +``` +terraform import rabbitmq_exchange.test test@vhost +``` diff --git a/website/source/docs/providers/rabbitmq/r/permissions.html.markdown b/website/source/docs/providers/rabbitmq/r/permissions.html.markdown new file mode 100644 index 000000000..4249addc6 --- /dev/null +++ b/website/source/docs/providers/rabbitmq/r/permissions.html.markdown @@ -0,0 +1,66 @@ +--- +layout: "rabbitmq" +page_title: "RabbitMQ: rabbitmq_permissions" +sidebar_current: "docs-rabbitmq-resource-permissions" +description: |- + Creates and manages a user's permissions on a RabbitMQ server. +--- + +# rabbitmq\_permissions + +The ``rabbitmq_permissions`` resource creates and manages a user's set of +permissions. + +## Example Usage + +``` +resource "rabbitmq_vhost" "test" { + name = "test" +} + +resource "rabbitmq_user" "test" { + name = "mctest" + password = "foobar" + tags = ["administrator"] +} + +resource "rabbitmq_permissions" "test" { + user = "${rabbitmq_user.test.name}" + vhost = "${rabbitmq_vhost.test.name}" + permissions { + configure = ".*" + write = ".*" + read = ".*" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `user` - (Required) The user to apply the permissions to. + +* `vhost` - (Required) The vhost to create the resource in. + +* `permissions` - (Required) The settings of the permissions. The structure is + described below. + +The `permissions` block supports: + +* `configure` - (Required) The "configure" ACL. +* `write` - (Required) The "write" ACL. +* `read` - (Required) The "read" ACL. + +## Attributes Reference + +No further attributes are exported. + +## Import + +Permissions can be imported using the `id` which is composed of `user@vhost`. +E.g. + +``` +terraform import rabbitmq_permissions.test user@vhost +``` diff --git a/website/source/docs/providers/rabbitmq/r/policy.html.markdown b/website/source/docs/providers/rabbitmq/r/policy.html.markdown new file mode 100644 index 000000000..48226cab7 --- /dev/null +++ b/website/source/docs/providers/rabbitmq/r/policy.html.markdown @@ -0,0 +1,75 @@ +--- +layout: "rabbitmq" +page_title: "RabbitMQ: rabbitmq_policy" +sidebar_current: "docs-rabbitmq-resource-policy" +description: |- + Creates and manages a policy on a RabbitMQ server. +--- + +# rabbitmq\_policy + +The ``rabbitmq_policy`` resource creates and manages policies for exchanges +and queues. + +## Example Usage + +``` +resource "rabbitmq_vhost" "test" { + name = "test" +} + +resource "rabbitmq_permissions" "guest" { + user = "guest" + vhost = "${rabbitmq_vhost.test.name}" + permissions { + configure = ".*" + write = ".*" + read = ".*" + } +} + +resource "rabbitmq_policy" "test" { + name = "test" + vhost = "${rabbitmq_permissions.guest.vhost}" + policy { + pattern = ".*" + priority = 0 + apply_to = "all" + definition { + ha-mode = "all" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the policy. + +* `vhost` - (Required) The vhost to create the resource in. + +* `policy` - (Required) The settings of the policy. The structure is + described below. + +The `policy` block supports: + +* `pattern` - (Required) A pattern to match an exchange or queue name. +* `priority` - (Required) The policy with the greater priority is applied first. +* `apply_to` - (Required) Can either be "exchange", "queues", or "all". +* `definition` - (Required) Key/value pairs of the policy definition. See the + RabbitMQ documentation for definition references and examples. + +## Attributes Reference + +No further attributes are exported. + +## Import + +Policies can be imported using the `id` which is composed of `name@vhost`. +E.g. + +``` +terraform import rabbitmq_policy.test name@vhost +``` diff --git a/website/source/docs/providers/rabbitmq/r/queue.html.markdown b/website/source/docs/providers/rabbitmq/r/queue.html.markdown new file mode 100644 index 000000000..c1b3287dc --- /dev/null +++ b/website/source/docs/providers/rabbitmq/r/queue.html.markdown @@ -0,0 +1,71 @@ +--- +layout: "rabbitmq" +page_title: "RabbitMQ: rabbitmq_queue" +sidebar_current: "docs-rabbitmq-resource-queue" +description: |- + Creates and manages a queue on a RabbitMQ server. +--- + +# rabbitmq\_queue + +The ``rabbitmq_queue`` resource creates and manages a queue. + +## Example Usage + +``` +resource "rabbitmq_vhost" "test" { + name = "test" +} + +resource "rabbitmq_permissions" "guest" { + user = "guest" + vhost = "${rabbitmq_vhost.test.name}" + permissions { + configure = ".*" + write = ".*" + read = ".*" + } +} + +resource "rabbitmq_queue" "test" { + name = "test" + vhost = "${rabbitmq_permissions.guest.vhost}" + settings { + durable = false + auto_delete = true + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the queue. + +* `vhost` - (Required) The vhost to create the resource in. + +* `settings` - (Required) The settings of the queue. The structure is + described below. + +The `settings` block supports: + +* `durable` - (Optional) Whether the queue survives server restarts. + Defaults to `false`. + +* `auto_delete` - (Optional) Whether the queue will self-delete when all + consumers have unsubscribed. + +* `arguments` - (Optional) Additional key/value settings for the queue. + +## Attributes Reference + +No further attributes are exported. + +## Import + +Queues can be imported using the `id` which is composed of `name@vhost`. E.g. + +``` +terraform import rabbitmq_queue.test name@vhost +``` diff --git a/website/source/docs/providers/rabbitmq/r/user.html.markdown b/website/source/docs/providers/rabbitmq/r/user.html.markdown new file mode 100644 index 000000000..790229430 --- /dev/null +++ b/website/source/docs/providers/rabbitmq/r/user.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "rabbitmq" +page_title: "RabbitMQ: rabbitmq_user" +sidebar_current: "docs-rabbitmq-resource-user" +description: |- + Creates and manages a user on a RabbitMQ server. +--- + +# rabbitmq\_user + +The ``rabbitmq_user`` resource creates and manages a user. + +## Example Usage + +``` +resource "rabbitmq_user" "test" { + name = "mctest" + password = "foobar" + tags = ["administrator", "management"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the user. + +* `password` - (Required) The password of the user. The value of this argument + is plain-text so make sure to secure where this is defined. + +* `tags` - (Required) Which permission model to apply to the user. Valid + options are: management, policymaker, monitoring, and administrator. + +## Attributes Reference + +No further attributes are exported. + +## Import + +Users can be imported using the `name`, e.g. + +``` +terraform import rabbitmq_user.test mctest +``` diff --git a/website/source/docs/providers/rabbitmq/r/vhost.html.markdown b/website/source/docs/providers/rabbitmq/r/vhost.html.markdown new file mode 100644 index 000000000..61c236507 --- /dev/null +++ b/website/source/docs/providers/rabbitmq/r/vhost.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "rabbitmq" +page_title: "RabbitMQ: rabbitmq_vhost" +sidebar_current: "docs-rabbitmq-resource-vhost" +description: |- + Creates and manages a vhost on a RabbitMQ server. +--- + +# rabbitmq\_vhost + +The ``rabbitmq_vhost`` resource creates and manages a vhost. + +## Example Usage + +``` +resource "rabbitmq_vhost" "my_vhost" { + name = "my_vhost" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the vhost. + +## Attributes Reference + +No further attributes are exported. + +## Import + +Vhosts can be imported using the `name`, e.g. + +``` +terraform import rabbitmq_vhost.my_vhost my_vhost +``` diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index f97a9f46f..fbcf6c879 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -286,6 +286,10 @@ PowerDNS + > + RabbitMQ + + > Random diff --git a/website/source/layouts/rabbitmq.erb b/website/source/layouts/rabbitmq.erb new file mode 100644 index 000000000..564b733d7 --- /dev/null +++ b/website/source/layouts/rabbitmq.erb @@ -0,0 +1,44 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> + <% end %>