From b7c9dedf4ee4949af7ff09a3e6169bee3c5c748e Mon Sep 17 00:00:00 2001 From: Sean Chittenden Date: Tue, 14 Feb 2017 23:14:58 -0800 Subject: [PATCH] Add a `consul_catalog_services` resource that enumerates the list of services in a given Consul datacenter. --- .../data_source_consul_catalog_service.go | 13 +-- .../data_source_consul_catalog_services.go | 90 +++++++++++++++++++ ...ata_source_consul_catalog_services_test.go | 36 ++++++++ builtin/providers/consul/resource_provider.go | 9 +- 4 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 builtin/providers/consul/data_source_consul_catalog_services.go create mode 100644 builtin/providers/consul/data_source_consul_catalog_services_test.go diff --git a/builtin/providers/consul/data_source_consul_catalog_service.go b/builtin/providers/consul/data_source_consul_catalog_service.go index 6408fca34..7f41b66a3 100644 --- a/builtin/providers/consul/data_source_consul_catalog_service.go +++ b/builtin/providers/consul/data_source_consul_catalog_service.go @@ -9,7 +9,7 @@ import ( ) const ( - queryOptServicesAttr = "services" + catalogServiceElem = "services" catalogServiceCreateIndex = "create_index" catalogServiceDatacenter = "datacenter" @@ -37,13 +37,15 @@ func dataSourceConsulCatalogService() *schema.Resource { Schema: map[string]*schema.Schema{ // Data Source Predicate(s) catalogServiceDatacenter: &schema.Schema{ - // Used in the query, must be stored and force a refresh if the value changes. + // Used in the query, must be stored and force a refresh if the value + // changes. Computed: true, Type: schema.TypeString, ForceNew: true, }, catalogServiceTag: &schema.Schema{ - // Used in the query, must be stored and force a refresh if the value changes. + // Used in the query, must be stored and force a refresh if the value + // changes. Computed: true, Type: schema.TypeString, ForceNew: true, @@ -55,7 +57,7 @@ func dataSourceConsulCatalogService() *schema.Resource { queryOpts: schemaQueryOpts, // Out parameters - queryOptServicesAttr: &schema.Schema{ + catalogServiceElem: &schema.Schema{ Computed: true, Type: schema.TypeList, Elem: &schema.Resource{ @@ -158,7 +160,6 @@ func dataSourceConsulCatalogServiceRead(d *schema.ResourceData, meta interface{} } l := make([]interface{}, 0, len(services)) - for _, service := range services { const defaultServiceAttrs = 13 m := make(map[string]interface{}, defaultServiceAttrs) @@ -197,7 +198,7 @@ func dataSourceConsulCatalogServiceRead(d *schema.ResourceData, meta interface{} d.Set(catalogServiceDatacenter, queryOpts.Datacenter) d.Set(catalogServiceTag, serviceTag) - if err := d.Set(queryOptServicesAttr, l); err != nil { + if err := d.Set(catalogServiceElem, l); err != nil { return errwrap.Wrapf("Unable to store services: {{err}}", err) } diff --git a/builtin/providers/consul/data_source_consul_catalog_services.go b/builtin/providers/consul/data_source_consul_catalog_services.go new file mode 100644 index 000000000..dd4e7bedc --- /dev/null +++ b/builtin/providers/consul/data_source_consul_catalog_services.go @@ -0,0 +1,90 @@ +package consul + +import ( + "fmt" + "sort" + "strings" + + consulapi "github.com/hashicorp/consul/api" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" +) + +const ( + catalogServicesDatacenter = "datacenter" + catalogServicesNames = "names" + + catalogServicesServiceName = "name" + catalogServicesServiceTags = "tags" +) + +func dataSourceConsulCatalogServices() *schema.Resource { + return &schema.Resource{ + Read: dataSourceConsulCatalogServicesRead, + Schema: map[string]*schema.Schema{ + // Data Source Predicate(s) + catalogServicesDatacenter: &schema.Schema{ + // Used in the query, must be stored and force a refresh if the value + // changes. + Computed: true, + Type: schema.TypeString, + ForceNew: true, + }, + queryOpts: schemaQueryOpts, + + // Out parameters + catalogServicesNames: &schema.Schema{ + Computed: true, + Type: schema.TypeMap, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + catalogServiceServiceTags: &schema.Schema{ + // FIXME(sean@): Tags is currently a space separated list of tags. + // The ideal structure should be map[string][]string instead. + // When this is supported in the future this should be changed to + // be a TypeList instead. + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceConsulCatalogServicesRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*consulapi.Client) + + // Parse out data source filters to populate Consul's query options + queryOpts, err := getQueryOpts(d, client) + if err != nil { + return errwrap.Wrapf("unable to get query options for fetching catalog services: {{err}}", err) + } + + services, meta, err := client.Catalog().Services(queryOpts) + if err != nil { + return err + } + + m := make(map[string]interface{}, len(services)) + for name, tags := range services { + tagList := make([]string, 0, len(tags)) + for _, tag := range tags { + tagList = append(tagList, tag) + } + + sort.Strings(tagList) + m[name] = strings.Join(tagList, " ") + } + + const idKeyFmt = "catalog-services-%s" + d.SetId(fmt.Sprintf(idKeyFmt, queryOpts.Datacenter)) + + d.Set(catalogServicesDatacenter, queryOpts.Datacenter) + if err := d.Set(catalogServicesNames, m); err != nil { + return errwrap.Wrapf("Unable to store services: {{err}}", err) + } + + return nil +} diff --git a/builtin/providers/consul/data_source_consul_catalog_services_test.go b/builtin/providers/consul/data_source_consul_catalog_services_test.go new file mode 100644 index 000000000..661a9b96b --- /dev/null +++ b/builtin/providers/consul/data_source_consul_catalog_services_test.go @@ -0,0 +1,36 @@ +package consul + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataConsulCatalogServices_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataConsulCatalogServicesConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckDataSourceValue("data.consul_catalog_services.read", "datacenter", "dc1"), + testAccCheckDataSourceValue("data.consul_catalog_services.read", "names.%", "1"), + testAccCheckDataSourceValue("data.consul_catalog_services.read", "names.consul", ""), + ), + }, + }, + }) +} + +const testAccDataConsulCatalogServicesConfig = ` +data "consul_catalog_services" "read" { + query_options { + allow_stale = true + require_consistent = false + token = "" + wait_index = 0 + wait_time = "1m" + } +} +` diff --git a/builtin/providers/consul/resource_provider.go b/builtin/providers/consul/resource_provider.go index 635883d82..fb316adcc 100644 --- a/builtin/providers/consul/resource_provider.go +++ b/builtin/providers/consul/resource_provider.go @@ -64,10 +64,11 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ - "consul_agent_self": dataSourceConsulAgentSelf(), - "consul_catalog_nodes": dataSourceConsulCatalogNodes(), - "consul_catalog_service": dataSourceConsulCatalogService(), - "consul_keys": dataSourceConsulKeys(), + "consul_agent_self": dataSourceConsulAgentSelf(), + "consul_catalog_nodes": dataSourceConsulCatalogNodes(), + "consul_catalog_service": dataSourceConsulCatalogService(), + "consul_catalog_services": dataSourceConsulCatalogServices(), + "consul_keys": dataSourceConsulKeys(), }, ResourcesMap: map[string]*schema.Resource{