From 3c28e99060fd39fafde630349547d09941410baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Wed, 9 Jan 2019 12:12:03 +0100 Subject: [PATCH] Add consul_autopilot_health data source See https://github.com/terraform-providers/terraform-provider-consul/issues/46#issue-319347046 --- consul/data_source_consul_autopilot_health.go | 152 ++++++++++++++++++ ...ata_source_consul_autopilot_health_test.go | 90 +++++++++++ consul/resource_provider.go | 15 +- website/consul.erb | 4 + website/docs/d/autopilot_health.html.markdown | 57 +++++++ 5 files changed, 311 insertions(+), 7 deletions(-) create mode 100644 consul/data_source_consul_autopilot_health.go create mode 100644 consul/data_source_consul_autopilot_health_test.go create mode 100644 website/docs/d/autopilot_health.html.markdown diff --git a/consul/data_source_consul_autopilot_health.go b/consul/data_source_consul_autopilot_health.go new file mode 100644 index 00000000..80fe2907 --- /dev/null +++ b/consul/data_source_consul_autopilot_health.go @@ -0,0 +1,152 @@ +package consul + +import ( + "fmt" + + consulapi "github.com/hashicorp/consul/api" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" +) + +const ( + autopilotHealthDatacenter = "datacenter" + autopilotHealthHealthy = "healthy" + autopilotHealthFailureTolerance = "failure_tolerance" + autopilotHealthServers = "servers" + autopilotHealthServerID = "id" + autopilotHealthServerName = "name" + autopilotHealthServerAddress = "address" + autopilotHealthServerSerfStatus = "serf_status" + autopilotHealthServerVersion = "version" + autopilotHealthServerLeader = "leader" + autopilotHealthServerLastContact = "last_contact" + autopilotHealthServerLastTerm = "last_term" + autopilotHealthServerLastIndex = "last_index" + autopilotHealthServerHealthy = "healthy" + autopilotHealthServerVoter = "voter" + autopilotHealthServerStableSince = "stable_since" +) + +func dataSourceConsulAutopilotHealth() *schema.Resource { + return &schema.Resource{ + Read: dataSourceConsulAutopilotHealthRead, + Schema: map[string]*schema.Schema{ + // Filters + autopilotHealthDatacenter: &schema.Schema{ + Optional: true, + Type: schema.TypeString, + }, + + // Out parameters + autopilotHealthHealthy: &schema.Schema{ + Computed: true, + Type: schema.TypeBool, + }, + autopilotHealthFailureTolerance: &schema.Schema{ + Computed: true, + Type: schema.TypeInt, + }, + autopilotHealthServers: &schema.Schema{ + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + autopilotHealthServerID: &schema.Schema{ + Computed: true, + Type: schema.TypeString, + }, + autopilotHealthServerName: &schema.Schema{ + Computed: true, + Type: schema.TypeString, + }, + autopilotHealthServerAddress: &schema.Schema{ + Computed: true, + Type: schema.TypeString, + }, + autopilotHealthServerSerfStatus: &schema.Schema{ + Computed: true, + Type: schema.TypeString, + }, + autopilotHealthServerVersion: &schema.Schema{ + Computed: true, + Type: schema.TypeString, + }, + autopilotHealthServerLeader: &schema.Schema{ + Computed: true, + Type: schema.TypeBool, + }, + autopilotHealthServerLastContact: &schema.Schema{ + Computed: true, + Type: schema.TypeString, + }, + autopilotHealthServerLastTerm: &schema.Schema{ + Computed: true, + Type: schema.TypeInt, + }, + autopilotHealthServerLastIndex: &schema.Schema{ + Computed: true, + Type: schema.TypeInt, + }, + autopilotHealthServerHealthy: &schema.Schema{ + Computed: true, + Type: schema.TypeBool, + }, + autopilotHealthServerVoter: &schema.Schema{ + Computed: true, + Type: schema.TypeBool, + }, + autopilotHealthServerStableSince: &schema.Schema{ + Computed: true, + Type: schema.TypeString, + }, + }, + }, + }, + }, + } +} + +func dataSourceConsulAutopilotHealthRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*consulapi.Client) + operator := client.Operator() + + queryOpts, err := getQueryOpts(d, client) + if datacenter, ok := d.GetOk(autopilotHealthDatacenter); ok { + queryOpts.Datacenter = datacenter.(string) + } + + health, err := operator.AutopilotServerHealth(queryOpts) + if err != nil { + return err + } + const idKeyFmt = "autopilot-health-%s" + d.SetId(fmt.Sprintf(idKeyFmt, queryOpts.Datacenter)) + + d.Set("healthy", health.Healthy) + d.Set("failure_tolerance", health.FailureTolerance) + + serversHealth := make([]interface{}, 0, len(health.Servers)) + for _, server := range health.Servers { + h := make(map[string]interface{}, 12) + + h[autopilotHealthServerID] = server.ID + h[autopilotHealthServerName] = server.Name + h[autopilotHealthServerAddress] = server.Address + h[autopilotHealthServerSerfStatus] = server.SerfStatus + h[autopilotHealthServerVersion] = server.Version + h[autopilotHealthServerLeader] = server.Leader + h[autopilotHealthServerLastContact] = server.LastContact.String() + h[autopilotHealthServerLastTerm] = server.LastTerm + h[autopilotHealthServerLastIndex] = server.LastIndex + h[autopilotHealthServerHealthy] = server.Healthy + h[autopilotHealthServerVoter] = server.Voter + h[autopilotHealthServerStableSince] = server.StableSince.String() + + serversHealth = append(serversHealth, h) + } + + if err := d.Set("servers", serversHealth); err != nil { + return errwrap.Wrapf("Unable to store servers health: {{err}}", err) + } + return nil +} diff --git a/consul/data_source_consul_autopilot_health_test.go b/consul/data_source_consul_autopilot_health_test.go new file mode 100644 index 00000000..6fa2e683 --- /dev/null +++ b/consul/data_source_consul_autopilot_health_test.go @@ -0,0 +1,90 @@ +package consul + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataConsulAutopilotHealth_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataAutopilotHealth, + Check: resource.ComposeTestCheckFunc( + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "healthy", "true"), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "failure_tolerance", "0"), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.#", "1"), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.id", ""), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.name", ""), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.address", ""), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.serf_status", "alive"), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.version", ""), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.leader", "true"), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.last_contact", ""), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.last_term", ""), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.last_index", ""), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.healthy", "true"), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.voter", "true"), + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.0.stable_since", ""), + ), + }, + }, + }) +} + +func TestAccDataConsulAutopilotHealth_config(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataAutopilotHealthDatacenter, + Check: resource.ComposeTestCheckFunc( + testAccCheckDataSourceValue("data.consul_autopilot_health.read", "servers.#", "1"), + ), + }, + }, + }) +} + +func TestAccDataConsulAutopilotHealth_wrongDatacenter(t *testing.T) { + re, err := regexp.Compile("No path to datacenter") + if err != nil { + t.Fatalf("err: %#v", err) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataAutopilotHealthWrongDatacenter, + ExpectError: re, + }, + }, + }) +} + +const testAccDataAutopilotHealth = ` +data "consul_autopilot_health" "read" {} + +output "health" { + value = "${data.consul_autopilot_health.read.healthy}" +} +` + +const testAccDataAutopilotHealthDatacenter = ` +data "consul_autopilot_health" "read" { + datacenter = "dc1" +} +` + +const testAccDataAutopilotHealthWrongDatacenter = ` +data "consul_autopilot_health" "read" { + datacenter = "wrong_datacenter" +} +` diff --git a/consul/resource_provider.go b/consul/resource_provider.go index f4505d0d..b3fa215c 100644 --- a/consul/resource_provider.go +++ b/consul/resource_provider.go @@ -76,13 +76,14 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ - "consul_agent_self": dataSourceConsulAgentSelf(), - "consul_agent_config": dataSourceConsulAgentConfig(), - "consul_nodes": dataSourceConsulNodes(), - "consul_service": dataSourceConsulService(), - "consul_services": dataSourceConsulServices(), - "consul_keys": dataSourceConsulKeys(), - "consul_key_prefix": dataSourceConsulKeyPrefix(), + "consul_agent_self": dataSourceConsulAgentSelf(), + "consul_agent_config": dataSourceConsulAgentConfig(), + "consul_autopilot_health": dataSourceConsulAutopilotHealth(), + "consul_nodes": dataSourceConsulNodes(), + "consul_service": dataSourceConsulService(), + "consul_services": dataSourceConsulServices(), + "consul_keys": dataSourceConsulKeys(), + "consul_key_prefix": dataSourceConsulKeyPrefix(), // Aliases to limit the impact of rename of catalog // datasources diff --git a/website/consul.erb b/website/consul.erb index 9b28ddf5..420688aa 100644 --- a/website/consul.erb +++ b/website/consul.erb @@ -25,6 +25,10 @@ consul_agent_config + > + consul_autopilot_health + + > consul_nodes diff --git a/website/docs/d/autopilot_health.html.markdown b/website/docs/d/autopilot_health.html.markdown new file mode 100644 index 00000000..cd4973e7 --- /dev/null +++ b/website/docs/d/autopilot_health.html.markdown @@ -0,0 +1,57 @@ +--- +layout: "consul" +page_title: "Consul: consul_autopilot_health" +sidebar_current: "docs-consul-data-source-autopilot-health" +description: |- + Provides health information of the autopilot. +--- + +# consul_autopilot_health + +The `consul_autopilot_health` data source returns +[autopilot health information](https://www.consul.io/api/operator/autopilot.html#read-health) +about the current Consul cluster. + +## Example Usage + +```hcl +data "consul_autopilot_health" "read" {} + +output "health" { + value = "${data.consul_autopilot_health.read.healthy}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `datacenter` - (Optional) The datacenter to use. This overrides the + datacenter in the provider setup and the agent's default datacenter. + +## Attributes Reference + +The following attributes are exported: + +* `healthy` - Whether all the servers in the cluster are currently healthy +* `failure_tolerance` - The number of redundant healthy servers that could fail +without causing an outage +* `servers` - A list of server health information. See below for details on the +available information. + +### Server health information +* `id` - The Raft ID of the server +* `name` - The node name of the server +* `address` - The address of the server +* `serf_status` - The status of the SerfHealth check of the server +* `version` - The Consul version of the server +* `leader` - Whether the server is currently leader +* `last_contact` - The time elapsed since the server's last contact with +the leader +* `last_term` - The server's last known Raft leader term +* `last_index` - The index of the server's last committed Raft log entry +* `healthy` - Whether the server is healthy according to the current Autopilot +configuration +* `voter` - Whether the server is a voting member of the Raft cluster +* `stable_since` - The time this server has been in its current ``Healthy`` +state