From 53e497567ecf90a858142af7a90f2394375ff571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Mon, 9 Oct 2023 06:16:19 -0700 Subject: [PATCH 1/5] Implement consul_config_entry_service_splitter Co-authored-by: Ashesh Vidyut --- .../resource_consul_config_entry_concrete.go | 129 +++++++++ ...ce_consul_config_entry_service_splitter.go | 270 ++++++++++++++++++ ...nsul_config_entry_service_splitter_test.go | 173 +++++++++++ consul/resource_provider.go | 53 ++-- .../config_entry_service_splitter.md | 156 ++++++++++ .../import.sh | 1 + .../resource.tf | 79 +++++ 7 files changed, 835 insertions(+), 26 deletions(-) create mode 100644 consul/resource_consul_config_entry_concrete.go create mode 100644 consul/resource_consul_config_entry_service_splitter.go create mode 100644 consul/resource_consul_config_entry_service_splitter_test.go create mode 100644 docs/resources/config_entry_service_splitter.md create mode 100644 examples/resources/consul_config_entry_service_splitter/import.sh create mode 100644 examples/resources/consul_config_entry_service_splitter/resource.tf diff --git a/consul/resource_consul_config_entry_concrete.go b/consul/resource_consul_config_entry_concrete.go new file mode 100644 index 00000000..ad4707ea --- /dev/null +++ b/consul/resource_consul_config_entry_concrete.go @@ -0,0 +1,129 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package consul + +import ( + "fmt" + "strings" + + consulapi "github.com/hashicorp/consul/api" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +// ConfigEntryImplementation is the common implementation for all specific +// config entries. +type ConfigEntryImplementation interface { + GetKind() string + GetDescription() string + GetSchema() map[string]*schema.Schema + Decode(d *schema.ResourceData) (consulapi.ConfigEntry, error) + Write(ce consulapi.ConfigEntry, d *schema.ResourceData, sw *stateWriter) error +} + +func resourceFromConfigEntryImplementation(c ConfigEntryImplementation) *schema.Resource { + return &schema.Resource{ + Description: c.GetDescription(), + Schema: c.GetSchema(), + Create: configEntryImplementationWrite(c), + Update: configEntryImplementationWrite(c), + Read: configEntryImplementationRead(c), + Delete: configEntryImplementationDelete(c), + Importer: &schema.ResourceImporter{ + State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + var name, partition, namespace string + switch len(parts) { + case 1: + name = parts[0] + case 3: + partition = parts[0] + namespace = parts[1] + name = parts[2] + default: + return nil, fmt.Errorf(`expected path of the form "" or "//"`) + } + + d.SetId(name) + sw := newStateWriter(d) + sw.set("name", name) + sw.set("partition", partition) + sw.set("namespace", namespace) + + err := sw.error() + if err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil + }, + }, + } +} + +func configEntryImplementationWrite(impl ConfigEntryImplementation) func(d *schema.ResourceData, meta interface{}) error { + return func(d *schema.ResourceData, meta interface{}) error { + client, qOpts, wOpts := getClient(d, meta) + + configEntry, err := impl.Decode(d) + if err != nil { + return err + } + + if _, _, err := client.ConfigEntries().Set(configEntry, wOpts); err != nil { + return fmt.Errorf("failed to set '%s' config entry: %v", configEntry.GetName(), err) + } + _, _, err = client.ConfigEntries().Get(configEntry.GetKind(), configEntry.GetName(), qOpts) + if err != nil { + if strings.Contains(err.Error(), "Unexpected response code: 404") { + return fmt.Errorf("failed to read config entry after setting it") + } + return fmt.Errorf("failed to read config entry: %v", err) + } + + d.SetId(configEntry.GetName()) + return configEntryImplementationRead(impl)(d, meta) + } +} + +func configEntryImplementationRead(impl ConfigEntryImplementation) func(d *schema.ResourceData, meta interface{}) error { + return func(d *schema.ResourceData, meta interface{}) error { + client, qOpts, _ := getClient(d, meta) + name := d.Get("name").(string) + + fixQOptsForConfigEntry(name, impl.GetKind(), qOpts) + + ce, _, err := client.ConfigEntries().Get(impl.GetKind(), name, qOpts) + if err != nil { + if strings.Contains(err.Error(), "Unexpected response code: 404") { + // The config entry has been removed + d.SetId("") + return nil + } + return fmt.Errorf("failed to fetch '%s' config entry: %v", name, err) + } + if ce == nil { + d.SetId("") + return nil + } + + sw := newStateWriter(d) + if err := impl.Write(ce, d, sw); err != nil { + return err + } + return sw.error() + } +} + +func configEntryImplementationDelete(impl ConfigEntryImplementation) func(d *schema.ResourceData, meta interface{}) error { + return func(d *schema.ResourceData, meta interface{}) error { + client, _, wOpts := getClient(d, meta) + name := d.Get("name").(string) + + if _, err := client.ConfigEntries().Delete(impl.GetKind(), name, wOpts); err != nil { + return fmt.Errorf("failed to delete '%s' config entry: %v", name, err) + } + d.SetId("") + return nil + } +} diff --git a/consul/resource_consul_config_entry_service_splitter.go b/consul/resource_consul_config_entry_service_splitter.go new file mode 100644 index 00000000..e18724a4 --- /dev/null +++ b/consul/resource_consul_config_entry_service_splitter.go @@ -0,0 +1,270 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package consul + +import ( + "fmt" + + consulapi "github.com/hashicorp/consul/api" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type serviceSplitter struct{} + +func (s *serviceSplitter) GetKind() string { + return consulapi.ServiceSplitter +} + +func (s *serviceSplitter) GetDescription() string { + return "The `consul_config_entry_service_splitter` resource configures a [service splitter](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-splitter) that will redirect a percentage of incoming traffic requests for a service to one or more specific service instances." +} + +func (s *serviceSplitter) GetSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "Specifies a name for the configuration entry.", + Required: true, + ForceNew: true, + }, + "partition": { + Type: schema.TypeString, + Description: "Specifies the admin partition to apply the configuration entry.", + Optional: true, + ForceNew: true, + }, + "namespace": { + Type: schema.TypeString, + Description: "Specifies the namespace to apply the configuration entry.", + Optional: true, + ForceNew: true, + }, + "meta": { + Type: schema.TypeMap, + Description: "Specifies key-value pairs to add to the KV store.", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "splits": { + Type: schema.TypeList, + Description: "Defines how much traffic to send to sets of service instances during a traffic split.", + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "weight": { + Type: schema.TypeFloat, + Description: "Specifies the percentage of traffic sent to the set of service instances specified in the `service` field. Each weight must be a floating integer between `0` and `100`. The smallest representable value is `.01`. The sum of weights across all splits must add up to `100`.", + Required: true, + }, + "service": { + Type: schema.TypeString, + Description: "Specifies the name of the service to resolve.", + Required: true, + }, + "service_subset": { + Type: schema.TypeString, + Description: "Specifies a subset of the service to resolve. A service subset assigns a name to a specific subset of discoverable service instances within a datacenter, such as `version2` or `canary`. All services have an unnamed default subset that returns all healthy instances.", + Optional: true, + }, + "namespace": { + Type: schema.TypeString, + Description: "Specifies the namespace to use in the FQDN when resolving the service.", + Optional: true, + }, + "partition": { + Type: schema.TypeString, + Description: "Specifies the admin partition to use in the FQDN when resolving the service.", + Optional: true, + }, + "request_headers": { + Type: schema.TypeList, + MaxItems: 1, + Description: "Specifies a set of HTTP-specific header modification rules applied to requests routed with the service split. You cannot configure request headers if the listener protocol is set to `tcp`.", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "add": { + Type: schema.TypeMap, + Description: "Map of one or more key-value pairs. Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers.", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "set": { + Type: schema.TypeMap, + Description: "Map of one or more key-value pairs. Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values.", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "remove": { + Type: schema.TypeList, + Description: "Defines an list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive.", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "response_headers": { + Type: schema.TypeList, + MaxItems: 1, + Description: "Specifies a set of HTTP-specific header modification rules applied to responses routed with the service split. You cannot configure request headers if the listener protocol is set to `tcp`.", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "add": { + Type: schema.TypeMap, + Description: "Map of one or more key-value pairs. Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers.", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "set": { + Type: schema.TypeMap, + Description: "Map of one or more key-value pairs. Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values.", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "remove": { + Type: schema.TypeList, + Description: "Defines an list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive.", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (s *serviceSplitter) Decode(d *schema.ResourceData) (consulapi.ConfigEntry, error) { + configEntry := &consulapi.ServiceSplitterConfigEntry{ + Kind: consulapi.ServiceSplitter, + Name: d.Get("name").(string), + Namespace: d.Get("namespace").(string), + Partition: d.Get("partition").(string), + Meta: map[string]string{}, + } + + for k, v := range d.Get("meta").(map[string]interface{}) { + configEntry.Meta[k] = v.(string) + } + + for _, raw := range d.Get("splits").([]interface{}) { + s := raw.(map[string]interface{}) + split := consulapi.ServiceSplit{ + Weight: float32(s["weight"].(float64)), + Service: s["service"].(string), + ServiceSubset: s["service_subset"].(string), + Namespace: s["namespace"].(string), + Partition: s["partition"].(string), + RequestHeaders: &consulapi.HTTPHeaderModifiers{ + Add: map[string]string{}, + Set: map[string]string{}, + }, + ResponseHeaders: &consulapi.HTTPHeaderModifiers{ + Add: map[string]string{}, + Set: map[string]string{}, + }, + } + + addHeaders := func(modifier *consulapi.HTTPHeaderModifiers, path string) { + elems := s[path].([]interface{}) + if len(elems) == 0 { + return + } + + headers := elems[0].(map[string]interface{}) + for k, v := range headers["add"].(map[string]interface{}) { + modifier.Add[k] = v.(string) + } + for k, v := range headers["set"].(map[string]interface{}) { + modifier.Set[k] = v.(string) + } + for _, v := range headers["remove"].([]interface{}) { + modifier.Remove = append(modifier.Remove, v.(string)) + } + } + addHeaders(split.RequestHeaders, "request_headers") + addHeaders(split.ResponseHeaders, "response_headers") + + configEntry.Splits = append(configEntry.Splits, split) + } + + return configEntry, nil +} + +func (s *serviceSplitter) Write(ce consulapi.ConfigEntry, d *schema.ResourceData, sw *stateWriter) error { + sp, ok := ce.(*consulapi.ServiceSplitterConfigEntry) + if !ok { + return fmt.Errorf("expected '%s' but got '%s'", consulapi.ServiceSplitter, ce.GetKind()) + } + + sw.set("name", sp.Name) + sw.set("partition", sp.Partition) + sw.set("namespace", sp.Namespace) + + meta := map[string]interface{}{} + for k, v := range sp.Meta { + meta[k] = v + } + sw.set("meta", meta) + + splits := make([]interface{}, 0) + for i, s := range sp.Splits { + split := map[string]interface{}{ + "weight": s.Weight, + "service": s.Service, + "service_subset": s.ServiceSubset, + "namespace": s.Namespace, + "partition": s.Partition, + } + addHeaders := func(modifier *consulapi.HTTPHeaderModifiers, path string) { + headers := map[string]interface{}{} + + shouldSet := func() bool { + splits := d.Get("splits").([]interface{}) + if len(splits) <= i { + return true + } + if _, found := splits[i].(map[string]interface{})[path]; !found { + return false + } + + return len(splits[i].(map[string]interface{})[path].([]interface{})) != 0 + }() + + if !shouldSet && len(modifier.Add)+len(modifier.Set)+len(modifier.Remove) == 0 { + return + } + + add := map[string]interface{}{} + for k, v := range modifier.Add { + add[k] = v + } + headers["add"] = add + + set := map[string]interface{}{} + for k, v := range modifier.Set { + set[k] = v + } + headers["set"] = set + + var remove []interface{} + for _, v := range modifier.Remove { + remove = append(remove, v) + } + headers["remove"] = remove + + split[path] = []interface{}{headers} + } + addHeaders(s.RequestHeaders, "request_headers") + addHeaders(s.ResponseHeaders, "response_headers") + splits = append(splits, split) + } + sw.set("splits", splits) + + return sw.error() +} diff --git a/consul/resource_consul_config_entry_service_splitter_test.go b/consul/resource_consul_config_entry_service_splitter_test.go new file mode 100644 index 00000000..21c1f300 --- /dev/null +++ b/consul/resource_consul_config_entry_service_splitter_test.go @@ -0,0 +1,173 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package consul + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccConsulConfigEntryServiceSplitterTest(t *testing.T) { + providers, _ := startTestServer(t) + + var config string + if serverIsConsulCommunityEdition(t) { + config = testConsulConfigEntryServiceSplitter("", "") + } else { + config = testConsulConfigEntryServiceSplitter("default", "default") + } + + resource.Test(t, resource.TestCase{ + Providers: providers, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "id", "web"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "meta.%", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "meta.key", "value"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "name", "web"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "namespace", ""), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "partition", ""), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.#", "3"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.namespace", ""), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.partition", ""), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.request_headers.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.request_headers.0.add.%", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.request_headers.0.remove.#", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.request_headers.0.set.%", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.request_headers.0.set.x-web-version", "from-v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.response_headers.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.response_headers.0.add.%", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.response_headers.0.remove.#", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.response_headers.0.set.%", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.response_headers.0.set.x-web-version", "to-v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.service", "web"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.service_subset", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.0.weight", "80"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.namespace", ""), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.partition", ""), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.request_headers.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.request_headers.0.add.%", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.request_headers.0.remove.#", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.request_headers.0.set.%", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.request_headers.0.set.x-web-version", "from-v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.response_headers.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.response_headers.0.add.%", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.response_headers.0.remove.#", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.response_headers.0.set.%", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.response_headers.0.set.x-web-version", "to-v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.service", "web"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.service_subset", "v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.1.weight", "10"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.2.namespace", ""), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.2.partition", ""), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.2.request_headers.#", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.2.response_headers.#", "0"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.2.service", "web"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.2.service_subset", "v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_splitter.foo", "splits.2.weight", "10"), + ), + }, + { + Config: config, + ResourceName: "consul_config_entry_service_splitter.foo", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "splits.2.request_headers.#", + "splits.2.response_headers.#", + }, + }, + }, + }) +} + +func testConsulConfigEntryServiceSplitter(namespace, partition string) string { + return fmt.Sprintf(` +resource "consul_config_entry" "web" { + name = "web" + kind = "service-defaults" + + config_json = jsonencode({ + Protocol = "http" + Expose = {} + MeshGateway = {} + TransparentProxy = {} + }) +} + +resource "consul_config_entry" "service_resolver" { + kind = "service-resolver" + name = consul_config_entry.web.name + + config_json = jsonencode({ + DefaultSubset = "v1" + + Subsets = { + "v1" = { + Filter = "Service.Meta.version == v1" + } + "v2" = { + Filter = "Service.Meta.version == v2" + } + } + }) +} + +resource "consul_config_entry_service_splitter" "foo" { + name = consul_config_entry.service_resolver.name + namespace = "%s" + partition = "%s" + + meta = { + key = "value" + } + + splits { + weight = 80 + service = "web" + service_subset = "v1" + + request_headers { + set = { + "x-web-version" = "from-v1" + } + } + + response_headers { + set = { + "x-web-version" = "to-v1" + } + } + } + + splits { + weight = 10 + service = "web" + service_subset = "v2" + + request_headers { + set = { + "x-web-version" = "from-v2" + } + } + + response_headers { + set = { + "x-web-version" = "to-v2" + } + } + } + + splits { + weight = 10 + service = "web" + service_subset = "v2" + } +} +`, namespace, partition) +} diff --git a/consul/resource_provider.go b/consul/resource_provider.go index 3fc9b62b..77697285 100644 --- a/consul/resource_provider.go +++ b/consul/resource_provider.go @@ -225,32 +225,33 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "consul_acl_auth_method": resourceConsulACLAuthMethod(), - "consul_acl_binding_rule": resourceConsulACLBindingRule(), - "consul_acl_policy": resourceConsulACLPolicy(), - "consul_acl_role": resourceConsulACLRole(), - "consul_acl_token": resourceConsulACLToken(), - "consul_acl_token_policy_attachment": resourceConsulACLTokenPolicyAttachment(), - "consul_acl_token_role_attachment": resourceConsulACLTokenRoleAttachment(), - "consul_admin_partition": resourceConsulAdminPartition(), - "consul_agent_service": resourceConsulAgentService(), - "consul_catalog_entry": resourceConsulCatalogEntry(), - "consul_certificate_authority": resourceConsulCertificateAuthority(), - "consul_config_entry": resourceConsulConfigEntry(), - "consul_keys": resourceConsulKeys(), - "consul_key_prefix": resourceConsulKeyPrefix(), - "consul_license": resourceConsulLicense(), - "consul_namespace": resourceConsulNamespace(), - "consul_namespace_policy_attachment": resourceConsulNamespacePolicyAttachment(), - "consul_namespace_role_attachment": resourceConsulNamespaceRoleAttachment(), - "consul_node": resourceConsulNode(), - "consul_prepared_query": resourceConsulPreparedQuery(), - "consul_autopilot_config": resourceConsulAutopilotConfig(), - "consul_service": resourceConsulService(), - "consul_intention": resourceConsulIntention(), - "consul_network_area": resourceConsulNetworkArea(), - "consul_peering_token": resourceSourceConsulPeeringToken(), - "consul_peering": resourceSourceConsulPeering(), + "consul_acl_auth_method": resourceConsulACLAuthMethod(), + "consul_acl_binding_rule": resourceConsulACLBindingRule(), + "consul_acl_policy": resourceConsulACLPolicy(), + "consul_acl_role": resourceConsulACLRole(), + "consul_acl_token_policy_attachment": resourceConsulACLTokenPolicyAttachment(), + "consul_acl_token_role_attachment": resourceConsulACLTokenRoleAttachment(), + "consul_acl_token": resourceConsulACLToken(), + "consul_admin_partition": resourceConsulAdminPartition(), + "consul_agent_service": resourceConsulAgentService(), + "consul_autopilot_config": resourceConsulAutopilotConfig(), + "consul_catalog_entry": resourceConsulCatalogEntry(), + "consul_certificate_authority": resourceConsulCertificateAuthority(), + "consul_config_entry_service_splitter": resourceFromConfigEntryImplementation(&serviceSplitter{}), + "consul_config_entry": resourceConsulConfigEntry(), + "consul_intention": resourceConsulIntention(), + "consul_key_prefix": resourceConsulKeyPrefix(), + "consul_keys": resourceConsulKeys(), + "consul_license": resourceConsulLicense(), + "consul_namespace_policy_attachment": resourceConsulNamespacePolicyAttachment(), + "consul_namespace_role_attachment": resourceConsulNamespaceRoleAttachment(), + "consul_namespace": resourceConsulNamespace(), + "consul_network_area": resourceConsulNetworkArea(), + "consul_node": resourceConsulNode(), + "consul_peering_token": resourceSourceConsulPeeringToken(), + "consul_peering": resourceSourceConsulPeering(), + "consul_prepared_query": resourceConsulPreparedQuery(), + "consul_service": resourceConsulService(), }, ConfigureFunc: providerConfigure, diff --git a/docs/resources/config_entry_service_splitter.md b/docs/resources/config_entry_service_splitter.md new file mode 100644 index 00000000..7ebe7314 --- /dev/null +++ b/docs/resources/config_entry_service_splitter.md @@ -0,0 +1,156 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "consul_config_entry_service_splitter Resource - terraform-provider-consul" +subcategory: "" +description: |- + The consul_config_entry_service_splitter resource configures a service splitter https://developer.hashicorp.com/consul/docs/connect/config-entries/service-splitter that will redirect a percentage of incoming traffic requests for a service to one or more specific service instances. +--- + +# consul_config_entry_service_splitter (Resource) + +The `consul_config_entry_service_splitter` resource configures a [service splitter](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-splitter) that will redirect a percentage of incoming traffic requests for a service to one or more specific service instances. + +## Example Usage + +```terraform +resource "consul_config_entry" "web" { + name = "web" + kind = "service-defaults" + + config_json = jsonencode({ + Protocol = "http" + Expose = {} + MeshGateway = {} + TransparentProxy = {} + }) +} + +resource "consul_config_entry" "service_resolver" { + kind = "service-resolver" + name = consul_config_entry.web.name + + config_json = jsonencode({ + DefaultSubset = "v1" + + Subsets = { + "v1" = { + Filter = "Service.Meta.version == v1" + } + "v2" = { + Filter = "Service.Meta.version == v2" + } + } + }) +} + +resource "consul_config_entry_service_splitter" "foo" { + name = consul_config_entry.service_resolver.name + + meta = { + key = "value" + } + + splits { + weight = 80 + service = "web" + service_subset = "v1" + + request_headers { + set = { + "x-web-version" = "from-v1" + } + } + + response_headers { + set = { + "x-web-version" = "to-v1" + } + } + } + + splits { + weight = 10 + service = "web" + service_subset = "v2" + + request_headers { + set = { + "x-web-version" = "from-v2" + } + } + + response_headers { + set = { + "x-web-version" = "to-v2" + } + } + } + + splits { + weight = 10 + service = "web" + service_subset = "v2" + } +} +``` + + +## Schema + +### Required + +- `name` (String) Specifies a name for the configuration entry. +- `splits` (Block List, Min: 1) Defines how much traffic to send to sets of service instances during a traffic split. (see [below for nested schema](#nestedblock--splits)) + +### Optional + +- `meta` (Map of String) Specifies key-value pairs to add to the KV store. +- `namespace` (String) Specifies the namespace to apply the configuration entry. +- `partition` (String) Specifies the admin partition to apply the configuration entry. + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `splits` + +Required: + +- `service` (String) Specifies the name of the service to resolve. +- `weight` (Number) Specifies the percentage of traffic sent to the set of service instances specified in the `service` field. Each weight must be a floating integer between `0` and `100`. The smallest representable value is `.01`. The sum of weights across all splits must add up to `100`. + +Optional: + +- `namespace` (String) Specifies the namespace to use in the FQDN when resolving the service. +- `partition` (String) Specifies the admin partition to use in the FQDN when resolving the service. +- `request_headers` (Block List, Max: 1) Specifies a set of HTTP-specific header modification rules applied to requests routed with the service split. You cannot configure request headers if the listener protocol is set to `tcp`. (see [below for nested schema](#nestedblock--splits--request_headers)) +- `response_headers` (Block List, Max: 1) Specifies a set of HTTP-specific header modification rules applied to responses routed with the service split. You cannot configure request headers if the listener protocol is set to `tcp`. (see [below for nested schema](#nestedblock--splits--response_headers)) +- `service_subset` (String) Specifies a subset of the service to resolve. A service subset assigns a name to a specific subset of discoverable service instances within a datacenter, such as `version2` or `canary`. All services have an unnamed default subset that returns all healthy instances. + + +### Nested Schema for `splits.request_headers` + +Optional: + +- `add` (Map of String) Map of one or more key-value pairs. Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. +- `remove` (List of String) Defines an list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. +- `set` (Map of String) Map of one or more key-value pairs. Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. + + + +### Nested Schema for `splits.response_headers` + +Optional: + +- `add` (Map of String) Map of one or more key-value pairs. Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. +- `remove` (List of String) Defines an list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. +- `set` (Map of String) Map of one or more key-value pairs. Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import consul_config_entry_service_splitter.foo web +``` diff --git a/examples/resources/consul_config_entry_service_splitter/import.sh b/examples/resources/consul_config_entry_service_splitter/import.sh new file mode 100644 index 00000000..2655436b --- /dev/null +++ b/examples/resources/consul_config_entry_service_splitter/import.sh @@ -0,0 +1 @@ +terraform import consul_config_entry_service_splitter.foo web diff --git a/examples/resources/consul_config_entry_service_splitter/resource.tf b/examples/resources/consul_config_entry_service_splitter/resource.tf new file mode 100644 index 00000000..a8541fc2 --- /dev/null +++ b/examples/resources/consul_config_entry_service_splitter/resource.tf @@ -0,0 +1,79 @@ +resource "consul_config_entry" "web" { + name = "web" + kind = "service-defaults" + + config_json = jsonencode({ + Protocol = "http" + Expose = {} + MeshGateway = {} + TransparentProxy = {} + }) +} + +resource "consul_config_entry" "service_resolver" { + kind = "service-resolver" + name = consul_config_entry.web.name + + config_json = jsonencode({ + DefaultSubset = "v1" + + Subsets = { + "v1" = { + Filter = "Service.Meta.version == v1" + } + "v2" = { + Filter = "Service.Meta.version == v2" + } + } + }) +} + +resource "consul_config_entry_service_splitter" "foo" { + name = consul_config_entry.service_resolver.name + + meta = { + key = "value" + } + + splits { + weight = 80 + service = "web" + service_subset = "v1" + + request_headers { + set = { + "x-web-version" = "from-v1" + } + } + + response_headers { + set = { + "x-web-version" = "to-v1" + } + } + } + + splits { + weight = 10 + service = "web" + service_subset = "v2" + + request_headers { + set = { + "x-web-version" = "from-v2" + } + } + + response_headers { + set = { + "x-web-version" = "to-v2" + } + } + } + + splits { + weight = 10 + service = "web" + service_subset = "v2" + } +} From be1c0cb5d1a0ab135f8881df74bd700fb7f9135b Mon Sep 17 00:00:00 2001 From: absolutelightning Date: Tue, 10 Oct 2023 12:14:57 +0530 Subject: [PATCH 2/5] service resolvers --- ...ce_consul_config_entry_service_resolver.go | 649 ++++++++++++++++++ ...l_config_entry_service_resolver_ce_test.go | 169 +++++ ...l_config_entry_service_resolver_ee_test.go | 169 +++++ consul/resource_provider.go | 1 + go.mod | 10 +- go.sum | 28 +- 6 files changed, 1003 insertions(+), 23 deletions(-) create mode 100644 consul/resource_consul_config_entry_service_resolver.go create mode 100644 consul/resource_consul_config_entry_service_resolver_ce_test.go create mode 100644 consul/resource_consul_config_entry_service_resolver_ee_test.go diff --git a/consul/resource_consul_config_entry_service_resolver.go b/consul/resource_consul_config_entry_service_resolver.go new file mode 100644 index 00000000..4848075d --- /dev/null +++ b/consul/resource_consul_config_entry_service_resolver.go @@ -0,0 +1,649 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package consul + +import ( + "bytes" + "fmt" + "reflect" + "sort" + "strings" + "time" + + consulapi "github.com/hashicorp/consul/api" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type serviceResolver struct{} + +func (s *serviceResolver) GetKind() string { + return consulapi.ServiceResolver +} + +func (s *serviceResolver) GetDescription() string { + return "The `consul_config_entry_service_resolver` resource configures a [service resolver](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-resolver) that creates named subsets of service instances and define their behavior when satisfying upstream requests." +} + +func (s *serviceResolver) GetSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Specifies a name for the configuration entry.", + }, + "partition": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Specifies the admin partition that the service resolver applies to.", + }, + "namespace": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Specifies the namespace that the service resolver applies to.", + }, + "meta": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Specifies key-value pairs to add to the KV store.", + }, + "connect_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the timeout duration for establishing new network connections to this service.", + }, + "request_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the timeout duration for receiving an HTTP response from this service.", + }, + "subsets": { + Type: schema.TypeSet, + Optional: true, + Description: "Specifies names for custom service subsets and the conditions under which service instances belong to each subset.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of subset.", + }, + "filter": { + Type: schema.TypeString, + Required: true, + Description: "Specifies an expression that filters the DNS elements of service instances that belong to the subset. If empty, all healthy instances of a service are returned.", + }, + "only_passing": { + Type: schema.TypeBool, + Required: true, + Description: "Determines if instances that return a warning from a health check are allowed to resolve a request. When set to false, instances with passing and warning states are considered healthy. When set to true, only instances with a passing health check state are considered healthy.", + }, + }, + }, + }, + "default_subset": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies a defined subset of service instances to use when no explicit subset is requested. If this parameter is not specified, Consul uses the unnamed default subset.", + }, + "redirect": { + Type: schema.TypeSet, + Optional: true, + Description: "Specifies redirect instructions for local service traffic so that services deployed to a different network location resolve the upstream request instead.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the name of a service at the redirect’s destination that resolves local upstream requests.", + }, + "service_subset": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the name of a subset of services at the redirect’s destination that resolves local upstream requests. If empty, the default subset is used. If specified, you must also specify at least one of the following in the same Redirect map: Service, Namespace, andDatacenter.", + }, + "namespace": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the namespace at the redirect’s destination that resolves local upstream requests.", + }, + "partition": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the admin partition at the redirect’s destination that resolves local upstream requests.", + }, + "sameness_group": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the sameness group at the redirect’s destination that resolves local upstream requests.", + }, + "datacenter": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the datacenter at the redirect’s destination that resolves local upstream requests.", + }, + "peer": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the cluster with an active cluster peering connection at the redirect’s destination that resolves local upstream requests.", + }, + }, + }, + }, + "failover": { + Type: schema.TypeSet, + Optional: true, + Description: "Specifies controls for rerouting traffic to an alternate pool of service instances if the target service fails.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subset_name": { + Type: schema.TypeString, + Required: true, + Description: "Name of subset.", + }, + "service": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the name of the service to resolve at the failover location during a failover scenario.", + }, + "service_subset": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the name of a subset of service instances to resolve at the failover location during a failover scenario.", + }, + "namespace": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the namespace at the failover location where the failover services are deployed.", + }, + "sameness_group": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the sameness group at the failover location where the failover services are deployed.", + }, + "datacenters": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Specifies an ordered list of datacenters at the failover location to attempt connections to during a failover scenario. When Consul cannot establish a connection with the first datacenter in the list, it proceeds sequentially until establishing a connection with another datacenter.", + }, + "targets": { + Type: schema.TypeList, + Optional: true, + Description: "Specifies a fixed list of failover targets to try during failover. This list can express complicated failover scenarios.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the service name to use for the failover target. If empty, the current service name is used.", + }, + "service_subset": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the named subset to use for the failover target. If empty, the default subset for the requested service name is used.", + }, + "namespace": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the namespace to use for the failover target. If empty, the default namespace is used.", + }, + "partition": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the admin partition within the same datacenter to use for the failover target. If empty, the default partition is used.", + }, + "datacenter": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the WAN federated datacenter to use for the failover target. If empty, the current datacenter is used.", + }, + "peer": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the destination cluster peer to resolve the target service name from.", + }, + }, + }, + }, + }, + }, + Set: resourceConsulConfigEntryServiceResolverFailoverSetHash, + }, + "load_balancer": { + Type: schema.TypeSet, + Optional: true, + Description: "Specifies the load balancing policy and configuration for services issuing requests to this upstream.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "policy": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the type of load balancing policy for selecting a host. ", + }, + "least_request_config": { + Type: schema.TypeSet, + Optional: true, + Description: "Specifies configuration for the least_request policy type.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "choice_count": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "ring_hash_config": { + Type: schema.TypeSet, + Optional: true, + Description: "Specifies configuration for the ring_hash policy type.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "minimum_ring_size": { + Type: schema.TypeInt, + Optional: true, + Description: "Determines the minimum number of entries in the hash ring.", + }, + "maximum_ring_size": { + Type: schema.TypeInt, + Optional: true, + Description: "Determines the maximum number of entries in the hash ring.", + }, + }, + }, + }, + "hash_policies": { + Type: schema.TypeList, + Optional: true, + Description: "Specifies a list of hash policies to use for hashing load balancing algorithms. Consul evaluates hash policies individually and combines them so that identical lists result in the same hash.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "field": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the attribute type to hash on. You cannot specify the Field parameter if SourceIP is also configured.", + }, + "field_value": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the value to hash, such as a header name, cookie name, or a URL query parameter name.", + }, + "cookie_config": { + Type: schema.TypeSet, + Optional: true, + Description: "Specifies additional configuration options for the cookie hash policy type.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "session": { + Type: schema.TypeBool, + Optional: true, + Description: "Directs Consul to generate a session cookie with no expiration.", + }, + "ttl": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the TTL for generated cookies. Cannot be specified for session cookies.", + }, + "path": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the path to set for the cookie.", + }, + }, + }, + }, + "source_ip": { + Type: schema.TypeBool, + Optional: true, + Description: "Determines if the hash type should be source IP address.", + }, + "terminal": { + Type: schema.TypeBool, + Optional: true, + Description: "Determines if Consul should stop computing the hash when multiple hash policies are present.", + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceConsulConfigEntryServiceResolverFailoverSetHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + if m["subset_name"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["subset_name"].(string))) + } + if m["service"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["service"].(string))) + } + if m["service_subset"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["service_subset"].(string))) + } + if m["namespace"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["namespace"].(string))) + } + if m["sameness_group"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["sameness_group"].(string))) + } + if m["datacenters"] != nil { + datacenters := make([]string, 0) + if strings.HasPrefix(reflect.ValueOf(m["datacenters"]).String(), "<[]interface") { + for _, v := range m["datacenters"].([]interface{}) { + datacenters = append(datacenters, v.(string)) + } + } else { + for _, v := range m["datacenters"].([]string) { + datacenters = append(datacenters, v) + } + } + sort.Strings(datacenters) + for _, v := range datacenters { + buf.WriteString(fmt.Sprintf("%s-", v)) + } + } + if m["targets"] != nil { + for _, target := range m["targets"].([]interface{}) { + var keys []string + targetMap := target.(map[string]interface{}) + for k, _ := range targetMap { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + buf.WriteString(fmt.Sprintf("%s:%s-", k, targetMap[k].(string))) + } + } + } + return hashcode.String(buf.String()) +} + +func (s *serviceResolver) Decode(d *schema.ResourceData) (consulapi.ConfigEntry, error) { + configEntry := &consulapi.ServiceResolverConfigEntry{ + Kind: consulapi.ServiceResolver, + Name: d.Get("name").(string), + Partition: d.Get("partition").(string), + Namespace: d.Get("namespace").(string), + Meta: map[string]string{}, + } + + for k, v := range d.Get("meta").(map[string]interface{}) { + configEntry.Meta[k] = v.(string) + } + + connectTimeout, err := time.ParseDuration(d.Get("connect_timeout").(string)) + if err != nil { + return nil, err + } + configEntry.ConnectTimeout = connectTimeout + + requestTimeout, err := time.ParseDuration(d.Get("request_timeout").(string)) + if err != nil { + return nil, err + } + configEntry.RequestTimeout = requestTimeout + + subsets := make(map[string]consulapi.ServiceResolverSubset) + + subsetsList := d.Get("subsets").(*schema.Set).List() + for _, subset := range subsetsList { + subsetMap := subset.(map[string]interface{}) + var serviceResolverSubset consulapi.ServiceResolverSubset + serviceResolverSubset.Filter = subsetMap["filter"].(string) + serviceResolverSubset.OnlyPassing = subsetMap["only_passing"].(bool) + subsets[subsetMap["name"].(string)] = serviceResolverSubset + } + configEntry.Subsets = subsets + + configEntry.DefaultSubset = d.Get("default_subset").(string) + + if v := (d.Get("redirect").(*schema.Set)).List(); len(v) == 1 { + redirectMap := v[0].(map[string]interface{}) + var serviceResolverRedirect *consulapi.ServiceResolverRedirect + serviceResolverRedirect = new(consulapi.ServiceResolverRedirect) + serviceResolverRedirect.Service = redirectMap["service"].(string) + serviceResolverRedirect.ServiceSubset = redirectMap["service_subset"].(string) + serviceResolverRedirect.Namespace = redirectMap["namespace"].(string) + serviceResolverRedirect.Partition = redirectMap["partition"].(string) + serviceResolverRedirect.SamenessGroup = redirectMap["sameness_group"].(string) + serviceResolverRedirect.Datacenter = redirectMap["datacenter"].(string) + serviceResolverRedirect.Peer = redirectMap["peer"].(string) + configEntry.Redirect = serviceResolverRedirect + } + + failoverList := d.Get("failover").(*schema.Set).List() + failover := make(map[string]consulapi.ServiceResolverFailover) + for _, failoverElem := range failoverList { + failoverMap := failoverElem.(map[string]interface{}) + var serviceResolverFailover consulapi.ServiceResolverFailover + if value, ok := failoverMap["service"]; ok { + serviceResolverFailover.Service = value.(string) + } + if value, ok := failoverMap["service_subset"]; ok { + serviceResolverFailover.ServiceSubset = value.(string) + } + if value, ok := failoverMap["namespace"]; ok { + serviceResolverFailover.Namespace = value.(string) + } + if value, ok := failoverMap["sameness_group"]; ok { + serviceResolverFailover.SamenessGroup = value.(string) + } + if value, ok := failoverMap["datacenters"]; ok { + datacenters := make([]string, 0) + for _, v := range value.([]interface{}) { + datacenters = append(datacenters, v.(string)) + } + serviceResolverFailover.Datacenters = datacenters + } + if (failoverMap["targets"] != nil) && len(failoverMap["targets"].([]interface{})) > 0 { + serviceResolverFailoverTargets := make([]consulapi.ServiceResolverFailoverTarget, len(failoverMap["targets"].([]interface{}))) + for indx, target := range failoverMap["targets"].([]interface{}) { + targetElem := target.(map[string]interface{}) + var serviceResolverFailoverTarget consulapi.ServiceResolverFailoverTarget + if value, ok := targetElem["service"]; ok { + serviceResolverFailoverTarget.Service = value.(string) + } + if value, ok := targetElem["service_subset"]; ok { + serviceResolverFailoverTarget.ServiceSubset = value.(string) + } + if value, ok := targetElem["namespace"]; ok { + serviceResolverFailoverTarget.Namespace = value.(string) + } + if value, ok := targetElem["partition"]; ok { + serviceResolverFailoverTarget.Partition = value.(string) + } + if value, ok := targetElem["datacenter"]; ok { + serviceResolverFailoverTarget.Datacenter = value.(string) + } + if value, ok := targetElem["peer"]; ok { + serviceResolverFailoverTarget.Peer = value.(string) + } + serviceResolverFailoverTargets[indx] = serviceResolverFailoverTarget + } + serviceResolverFailover.Targets = serviceResolverFailoverTargets + } + failover[failoverMap["subset_name"].(string)] = serviceResolverFailover + } + configEntry.Failover = failover + + if lb := (d.Get("load_balancer").(*schema.Set)).List(); len(lb) == 1 { + loadBalancer := lb[0].(map[string]interface{}) + var ceLoadBalancer *consulapi.LoadBalancer + ceLoadBalancer = new(consulapi.LoadBalancer) + ceLoadBalancer.Policy = loadBalancer["policy"].(string) + if lrc := (loadBalancer["least_request_config"].(*schema.Set)).List(); len(lrc) == 1 { + var lreqConfig *consulapi.LeastRequestConfig + lreqConfig = new(consulapi.LeastRequestConfig) + lreqConfig.ChoiceCount = uint32(((lrc[0].(map[string]interface{}))["choice_count"]).(int)) + ceLoadBalancer.LeastRequestConfig = lreqConfig + } + if rhc := (loadBalancer["ring_hash_config"].(*schema.Set)).List(); len(rhc) == 1 { + var rhConfig *consulapi.RingHashConfig + rhConfig = new(consulapi.RingHashConfig) + rhConfig.MaximumRingSize = uint64(rhc[0].(map[string]interface{})["maximum_ring_size"].(int)) + rhConfig.MinimumRingSize = uint64(rhc[0].(map[string]interface{})["minimum_ring_size"].(int)) + ceLoadBalancer.RingHashConfig = rhConfig + } + if hp := loadBalancer["hash_policies"].([]interface{}); len(hp) > 0 { + hashPolicyList := make([]consulapi.HashPolicy, len(hp)) + for indx, hashPolicy := range hp { + hashPolicyMap := hashPolicy.(map[string]interface{}) + hashPolicyList[indx].Field = hashPolicyMap["field"].(string) + hashPolicyList[indx].FieldValue = hashPolicyMap["field_value"].(string) + var cookieConfig *consulapi.CookieConfig + cookieConfig = new(consulapi.CookieConfig) + if cc := hashPolicyMap["cookie_config"].(*schema.Set).List(); len(cc) == 1 { + cookieConfigMap := cc[0].(map[string]interface{}) + cookieConfig.Path = cookieConfigMap["path"].(string) + cookieConfig.Session = cookieConfigMap["session"].(bool) + ttl, err := time.ParseDuration(cookieConfigMap["ttl"].(string)) + if err != nil { + return nil, err + } + cookieConfig.TTL = ttl + hashPolicyList[indx].CookieConfig = cookieConfig + } + hashPolicyList[indx].SourceIP = hashPolicyMap["source_ip"].(bool) + hashPolicyList[indx].Terminal = hashPolicyMap["terminal"].(bool) + } + ceLoadBalancer.HashPolicies = hashPolicyList + } + configEntry.LoadBalancer = ceLoadBalancer + } + + return configEntry, nil +} + +func (s *serviceResolver) Write(ce consulapi.ConfigEntry, d *schema.ResourceData, sw *stateWriter) error { + sr, ok := ce.(*consulapi.ServiceResolverConfigEntry) + if !ok { + return fmt.Errorf("expected '%s' but got '%s'", consulapi.ServiceResolver, ce.GetKind()) + } + + sw.set("name", sr.Name) + sw.set("partition", sr.Partition) + sw.set("namespace", sr.Namespace) + + meta := map[string]interface{}{} + for k, v := range sr.Meta { + meta[k] = v + } + sw.set("meta", meta) + sw.set("connect_timeout", sr.ConnectTimeout.String()) + sw.set("request_timeout", sr.RequestTimeout.String()) + + subsets := make([]map[string]interface{}, len(sr.Subsets)) + indx := 0 + for name, _ := range sr.Subsets { + subsetMap := make(map[string]interface{}) + subsetMap["name"] = name + subsetSt := sr.Subsets[name] + subsetMap["filter"] = subsetSt.Filter + subsetMap["only_passing"] = subsetSt.OnlyPassing + subsets[indx] = subsetMap + indx++ + } + sw.set("subsets", subsets) + + sw.set("default_subset", sr.DefaultSubset) + + redirect := make([]map[string]interface{}, 1) + if sr.Redirect != nil { + redirect[0] = make(map[string]interface{}) + redirect[0]["service"] = sr.Redirect.Service + redirect[0]["service_subset"] = sr.Redirect.ServiceSubset + redirect[0]["namespace"] = sr.Redirect.Namespace + redirect[0]["partition"] = sr.Redirect.Partition + redirect[0]["sameness_group"] = sr.Redirect.SamenessGroup + redirect[0]["datacenter"] = sr.Redirect.Datacenter + redirect[0]["peer"] = sr.Redirect.Peer + sw.set("redirect", redirect) + } + + var failover *schema.Set + failover = new(schema.Set) + failover.F = resourceConsulConfigEntryServiceResolverFailoverSetHash + for name, failoverValue := range sr.Failover { + failoverMap := make(map[string]interface{}) + failoverMap["subset_name"] = name + failoverMap["service"] = failoverValue.Service + failoverMap["service_subset"] = failoverValue.ServiceSubset + failoverMap["namespace"] = failoverValue.Namespace + failoverMap["sameness_group"] = failoverValue.SamenessGroup + if len(failoverValue.Datacenters) > 0 { + failoverDatacenters := make([]string, len(failoverValue.Datacenters)) + for index, fd := range failoverValue.Datacenters { + failoverDatacenters[index] = fd + } + failoverMap["datacenters"] = failoverDatacenters + } + failoverTargets := make([]interface{}, len(sr.Failover[name].Targets)) + for index, ft := range sr.Failover[name].Targets { + failoverTargetMap := make(map[string]interface{}) + failoverTargetMap["service"] = ft.Service + failoverTargetMap["service_subset"] = ft.ServiceSubset + failoverTargetMap["namespace"] = ft.Namespace + failoverTargetMap["partition"] = ft.Partition + failoverTargetMap["datacenter"] = ft.Datacenter + failoverTargetMap["peer"] = ft.Peer + failoverTargets[index] = failoverTargetMap + } + failoverMap["targets"] = failoverTargets + failover.Add(failoverMap) + } + sw.set("failover", failover) + + if sr.LoadBalancer != nil { + loadBalancer := make([]map[string]interface{}, 1) + loadBalancer[0] = make(map[string]interface{}) + loadBalancer[0]["policy"] = sr.LoadBalancer.Policy + if sr.LoadBalancer.LeastRequestConfig != nil { + leastRequestConfig := make([]map[string]interface{}, 1) + leastRequestConfig[0] = make(map[string]interface{}) + leastRequestConfig[0]["choice_count"] = sr.LoadBalancer.LeastRequestConfig.ChoiceCount + loadBalancer[0]["least_request_config"] = leastRequestConfig + } + if sr.LoadBalancer.RingHashConfig != nil { + ringHashConfig := make([]map[string]interface{}, 1) + ringHashConfig[0] = make(map[string]interface{}) + ringHashConfig[0]["minimum_ring_size"] = sr.LoadBalancer.RingHashConfig.MinimumRingSize + ringHashConfig[0]["maximum_ring_size"] = sr.LoadBalancer.RingHashConfig.MaximumRingSize + loadBalancer[0]["ring_hash_config"] = ringHashConfig + } + + if sr.LoadBalancer.HashPolicies != nil { + hashPolicyList := make([]map[string]interface{}, len(sr.LoadBalancer.HashPolicies)) + for index, hashPolicy := range sr.LoadBalancer.HashPolicies { + hashPolicyMap := make(map[string]interface{}) + hashPolicyMap["field"] = hashPolicy.Field + hashPolicyMap["field_value"] = hashPolicy.FieldValue + if hashPolicy.CookieConfig != nil { + cookieConfigSet := make([]map[string]interface{}, 1) + cookieConfigSet[0] = make(map[string]interface{}) + cookieConfigSet[0]["session"] = hashPolicy.CookieConfig.Session + cookieConfigSet[0]["ttl"] = hashPolicy.CookieConfig.TTL + cookieConfigSet[0]["path"] = hashPolicy.CookieConfig.TTL + hashPolicyMap["cookie_config"] = cookieConfigSet + } + hashPolicyMap["source_ip"] = hashPolicy.SourceIP + hashPolicyMap["terminal"] = hashPolicy.Terminal + hashPolicyList[index] = hashPolicyMap + } + loadBalancer[0]["hash_policies"] = hashPolicyList + } + sw.set("load_balancer", loadBalancer) + } + + return nil +} diff --git a/consul/resource_consul_config_entry_service_resolver_ce_test.go b/consul/resource_consul_config_entry_service_resolver_ce_test.go new file mode 100644 index 00000000..c9686913 --- /dev/null +++ b/consul/resource_consul_config_entry_service_resolver_ce_test.go @@ -0,0 +1,169 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package consul + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccConsulConfigEntryServiceResolverCETest(t *testing.T) { + providers, _ := startTestServer(t) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipTestOnConsulEnterpriseEdition(t) }, + Providers: providers, + Steps: []resource.TestStep{ + { + Config: testConsulConfigEntryServiceResolverCEWithRedirect, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "name", "consul-service-resolver-1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "meta.key", "value"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "connect_timeout", "10s"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "request_timeout", "10s"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.#", "2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.1420492792.name", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.1420492792.filter", "Service.Meta.version == v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.853348911.only_passing", "true"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.853348911.name", "v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.853348911.filter", "Service.Meta.version == v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.853348911.only_passing", "true"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "default_subset", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "redirect.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "redirect.1000671749.datacenter", "dc1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.policy", "ring_hash"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.ring_hash_config.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.ring_hash_config.1100849243.minimum_ring_size", "3"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.ring_hash_config.1100849243.maximum_ring_size", "10"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.hash_policies.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.hash_policies.0.field", "header"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.hash_policies.0.field_value", "x-user-id"), + ), + }, + { + Config: testConsulConfigEntryServiceResolverCEWithFailover, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "name", "consul-service-resolver-2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "meta.key", "value"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "connect_timeout", "10s"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "request_timeout", "10s"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.#", "2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.1420492792.name", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.1420492792.filter", "Service.Meta.version == v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.1420492792.only_passing", "true"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.853348911.name", "v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.853348911.filter", "Service.Meta.version == v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.853348911.only_passing", "true"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "default_subset", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.#", "3"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1078808137.subset_name", "*"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1078808137.service", "backend"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1078808137.datacenters.0", "dc3"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1078808137.datacenters.1", "dc4"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1326363731.subset_name", "v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1326363731.service", "frontend"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1326363731.datacenters.0", "dc2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1420169321.subset_name", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1420169321.targets.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1420169321.targets.0.service_subset", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1420169321.targets.0.datacenter", "dc1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.policy", "ring_hash"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.ring_hash_config.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.ring_hash_config.1100849243.minimum_ring_size", "3"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.ring_hash_config.1100849243.maximum_ring_size", "10"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.hash_policies.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.hash_policies.0.field", "header"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.hash_policies.0.field_value", "x-user-id"), + ), + }, + }, + }) +} + +const testConsulConfigEntryServiceResolverCEWithRedirect = ` +resource "consul_config_entry_service_resolver" "foo" { + name = "consul-service-resolver-1" + meta = { + key: "value" + } + connect_timeout = "10s" + request_timeout = "10s" + subsets { + name = "v1" + filter = "Service.Meta.version == v1" + only_passing = true + } + subsets { + name = "v2" + filter = "Service.Meta.version == v2" + only_passing = true + } + default_subset = "v1" + redirect { + datacenter = "dc1" + } + load_balancer { + policy = "ring_hash" + ring_hash_config { + minimum_ring_size = 3 + maximum_ring_size = 10 + } + hash_policies { + field = "header" + field_value = "x-user-id" + } + } +} +` + +const testConsulConfigEntryServiceResolverCEWithFailover = ` +resource "consul_config_entry_service_resolver" "bar" { + name = "consul-service-resolver-2" + meta = { + key: "value" + } + connect_timeout = "10s" + request_timeout = "10s" + subsets { + name = "v1" + filter = "Service.Meta.version == v1" + only_passing = true + } + subsets { + name = "v2" + filter = "Service.Meta.version == v2" + only_passing = true + } + default_subset = "v1" + failover { + subset_name = "*" + service = "backend" + datacenters = ["dc3", "dc4"] + } + failover { + subset_name = "v2" + service = "frontend" + datacenters = ["dc2"] + } + failover { + subset_name = "v1" + targets { + service_subset = "v1" + datacenter = "dc1" + } + } + load_balancer { + policy = "ring_hash" + ring_hash_config { + minimum_ring_size = 3 + maximum_ring_size = 10 + } + hash_policies { + field = "header" + field_value = "x-user-id" + } + } +} +` diff --git a/consul/resource_consul_config_entry_service_resolver_ee_test.go b/consul/resource_consul_config_entry_service_resolver_ee_test.go new file mode 100644 index 00000000..47a76e30 --- /dev/null +++ b/consul/resource_consul_config_entry_service_resolver_ee_test.go @@ -0,0 +1,169 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package consul + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccConsulConfigEntryServiceResolverEETest(t *testing.T) { + providers, _ := startTestServer(t) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipTestOnConsulCommunityEdition(t) }, + Providers: providers, + Steps: []resource.TestStep{ + { + Config: testConsulConfigEntryServiceResolverEEWithRedirect, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "name", "consul-service-resolver-1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "meta.key", "value"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "connect_timeout", "10s"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "request_timeout", "10s"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.#", "2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.0.name", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.0.filter", "Service.Meta.version == v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.0.only_passing", "true"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.1.name", "v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.1.filter", "Service.Meta.version == v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "subsets.1.only_passing", "true"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "default_subset", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "redirect.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "redirect.1000671749.datacenter", "dc1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.policy", "ring_hash"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.ring_hash_config.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.ring_hash_config.1100849243.minimum_ring_size", "3"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.ring_hash_config.1100849243.maximum_ring_size", "10"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.hash_policies.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.hash_policies.0.field", "header"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.foo", "load_balancer.1867531597.hash_policies.0.field_value", "x-user-id"), + ), + }, + { + Config: testConsulConfigEntryServiceResolverEEWithFailover, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "name", "consul-service-resolver-2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "meta.key", "value"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "connect_timeout", "10s"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "request_timeout", "10s"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.#", "2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.0.name", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.0.filter", "Service.Meta.version == v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.0.only_passing", "true"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.1.name", "v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.1.filter", "Service.Meta.version == v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.1.only_passing", "true"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "default_subset", "v1"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.#", "3"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.0.subset_name", "*"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.0.service", "backend"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.0.datacenters", "[\"dc3\", \"dc4\"]"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1.subset_name", "v2"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1.service", "frontend"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1.datacenters", "[\"dc2\"]"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1.subset_name", "v1"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.2.targets.#", "1"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.2.targets.0.service_subset", "v1"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.2.targets.0.datacenter", "dc1"), + //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.2.datacenters", "[\"dc2\"]"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.policy", "ring_hash"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.ring_hash_config.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.ring_hash_config.1100849243.minimum_ring_size", "3"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.ring_hash_config.1100849243.maximum_ring_size", "10"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.hash_policies.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.hash_policies.0.field", "header"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.hash_policies.0.field_value", "x-user-id"), + ), + }, + }, + }) +} + +const testConsulConfigEntryServiceResolverEEWithRedirect = ` +resource "consul_config_entry_service_resolver" "foo" { + name = "consul-service-resolver-1" + meta = { + key: "value" + } + connect_timeout = "10s" + request_timeout = "10s" + subsets { + name = "v1" + filter = "Service.Meta.version == v1" + only_passing = true + } + subsets { + name = "v2" + filter = "Service.Meta.version == v2" + only_passing = true + } + default_subset = "v1" + redirect { + datacenter = "dc1" + } + load_balancer { + policy = "ring_hash" + ring_hash_config { + minimum_ring_size = 3 + maximum_ring_size = 10 + } + hash_policies { + field = "header" + field_value = "x-user-id" + } + } +} +` + +const testConsulConfigEntryServiceResolverEEWithFailover = ` +resource "consul_config_entry_service_resolver" "bar" { + name = "consul-service-resolver-2" + meta = { + key: "value" + } + connect_timeout = "10s" + request_timeout = "10s" + subsets { + name = "v1" + filter = "Service.Meta.version == v1" + only_passing = true + } + subsets { + name = "v2" + filter = "Service.Meta.version == v2" + only_passing = true + } + default_subset = "v1" + failover { + subset_name = "*" + service = "backend" + datacenters = ["dc3", "dc4"] + } + failover { + subset_name = "v2" + service = "frontend" + datacenters = ["dc2"] + } + failover { + subset_name = "v1" + targets { + service_subset = "v1" + datacenter = "dc1" + } + } + load_balancer { + policy = "ring_hash" + ring_hash_config { + minimum_ring_size = 3 + maximum_ring_size = 10 + } + hash_policies { + field = "header" + field_value = "x-user-id" + } + } +} +` diff --git a/consul/resource_provider.go b/consul/resource_provider.go index 77697285..dadca3cb 100644 --- a/consul/resource_provider.go +++ b/consul/resource_provider.go @@ -238,6 +238,7 @@ func Provider() terraform.ResourceProvider { "consul_catalog_entry": resourceConsulCatalogEntry(), "consul_certificate_authority": resourceConsulCertificateAuthority(), "consul_config_entry_service_splitter": resourceFromConfigEntryImplementation(&serviceSplitter{}), + "consul_config_entry_service_resolver": resourceFromConfigEntryImplementation(&serviceResolver{}), "consul_config_entry": resourceConsulConfigEntry(), "consul_intention": resourceConsulIntention(), "consul_key_prefix": resourceConsulKeyPrefix(), diff --git a/go.mod b/go.mod index b48e012d..5193be71 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/hashicorp/terraform-provider-consul require ( - github.com/hashicorp/consul/api v1.23.0 + github.com/hashicorp/consul/api v1.26.1-rc1 github.com/hashicorp/errwrap v1.1.0 github.com/hashicorp/terraform-plugin-sdk v1.17.2 github.com/mitchellh/mapstructure v1.5.0 @@ -12,7 +12,7 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v0.12.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect ) require ( @@ -39,7 +39,7 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.7.1 // indirect - github.com/hashicorp/consul/sdk v0.14.0 + github.com/hashicorp/consul/sdk v0.14.3-rc1 github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.7.0 // indirect @@ -88,9 +88,9 @@ require ( github.com/zclconf/go-cty-yaml v1.0.2 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.11.0 // indirect - golang.org/x/net v0.12.0 // indirect + golang.org/x/net v0.13.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sys v0.10.0 // indirect + golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.114.0 // indirect diff --git a/go.sum b/go.sum index 6becda55..7580fbb3 100644 --- a/go.sum +++ b/go.sum @@ -116,7 +116,6 @@ cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQn cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= @@ -348,7 +347,6 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -372,7 +370,6 @@ github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIG github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -409,10 +406,10 @@ github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6c github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.23.0 h1:L6e4v1AfoumqAHq/Rrsmuulev+nd7vltM3k8H329tyI= -github.com/hashicorp/consul/api v1.23.0/go.mod h1:SfvUIT74b0EplDuNgAJQ/FVqSO6KyK2ia80UI39/Ye8= -github.com/hashicorp/consul/sdk v0.14.0 h1:Hly+BMNMssVzoWddbBnBFi3W+Fzytvm0haSkihhj3GU= -github.com/hashicorp/consul/sdk v0.14.0/go.mod h1:gHYeuDa0+0qRAD6Wwr6yznMBvBwHKoxSBoW5l73+saE= +github.com/hashicorp/consul/api v1.26.1-rc1 h1:eO+53vwWxEV2TMbTVrJbXp2TJjJNv+Nxij8j+xi3yOc= +github.com/hashicorp/consul/api v1.26.1-rc1/go.mod h1:ZKnaWXL2r23i+SPmEj1H5YsRNUS/uUzB2uhmD2QY4IY= +github.com/hashicorp/consul/sdk v0.14.3-rc1 h1:kE0dLXXTnvm67PNRE527XpSwqi5vwWoP+VkNHdBBR7o= +github.com/hashicorp/consul/sdk v0.14.3-rc1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -434,7 +431,6 @@ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= -github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -448,7 +444,6 @@ github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhE github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -606,7 +601,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= @@ -627,7 +621,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -659,7 +652,6 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= @@ -720,8 +712,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -803,8 +795,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -924,8 +916,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From fb1d244b003253f102c58618ea5399a742f980e1 Mon Sep 17 00:00:00 2001 From: absolutelightning Date: Tue, 10 Oct 2023 12:16:56 +0530 Subject: [PATCH 3/5] update tests --- ...l_config_entry_service_resolver_ee_test.go | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/consul/resource_consul_config_entry_service_resolver_ee_test.go b/consul/resource_consul_config_entry_service_resolver_ee_test.go index 47a76e30..a6334bb5 100644 --- a/consul/resource_consul_config_entry_service_resolver_ee_test.go +++ b/consul/resource_consul_config_entry_service_resolver_ee_test.go @@ -57,18 +57,18 @@ func TestAccConsulConfigEntryServiceResolverEETest(t *testing.T) { resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.1.filter", "Service.Meta.version == v2"), resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "subsets.1.only_passing", "true"), resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "default_subset", "v1"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.#", "3"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.0.subset_name", "*"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.0.service", "backend"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.0.datacenters", "[\"dc3\", \"dc4\"]"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1.subset_name", "v2"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1.service", "frontend"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1.datacenters", "[\"dc2\"]"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1.subset_name", "v1"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.2.targets.#", "1"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.2.targets.0.service_subset", "v1"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.2.targets.0.datacenter", "dc1"), - //resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.2.datacenters", "[\"dc2\"]"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.#", "3"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1078808137.subset_name", "*"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1078808137.service", "backend"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1078808137.datacenters.0", "dc3"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1078808137.datacenters.1", "dc4"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1326363731.subset_name", "v2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1326363731.service", "frontend"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1326363731.datacenters.0", "dc2"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1420169321.subset_name", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1420169321.targets.#", "1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1420169321.targets.0.service_subset", "v1"), + resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "failover.1420169321.targets.0.datacenter", "dc1"), resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.policy", "ring_hash"), resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.ring_hash_config.#", "1"), resource.TestCheckResourceAttr("consul_config_entry_service_resolver.bar", "load_balancer.1867531597.ring_hash_config.1100849243.minimum_ring_size", "3"), From bb4ed42525f1891f66127c5e914be230c29da06e Mon Sep 17 00:00:00 2001 From: absolutelightning Date: Tue, 10 Oct 2023 12:29:56 +0530 Subject: [PATCH 4/5] docs --- .../config_entry_service_resolver.md | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 docs/resources/config_entry_service_resolver.md diff --git a/docs/resources/config_entry_service_resolver.md b/docs/resources/config_entry_service_resolver.md new file mode 100644 index 00000000..a43cfe73 --- /dev/null +++ b/docs/resources/config_entry_service_resolver.md @@ -0,0 +1,140 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "consul_config_entry_service_resolver Resource - terraform-provider-consul" +subcategory: "" +description: |- + The consul_config_entry_service_resolver resource configures a service resolver https://developer.hashicorp.com/consul/docs/connect/config-entries/service-resolver that creates named subsets of service instances and define their behavior when satisfying upstream requests. +--- + +# consul_config_entry_service_resolver (Resource) + +The `consul_config_entry_service_resolver` resource configures a [service resolver](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-resolver) that creates named subsets of service instances and define their behavior when satisfying upstream requests. + + + + +## Schema + +### Required + +- `name` (String) Specifies a name for the configuration entry. + +### Optional + +- `connect_timeout` (String) Specifies the timeout duration for establishing new network connections to this service. +- `default_subset` (String) Specifies a defined subset of service instances to use when no explicit subset is requested. If this parameter is not specified, Consul uses the unnamed default subset. +- `failover` (Block Set) Specifies controls for rerouting traffic to an alternate pool of service instances if the target service fails. (see [below for nested schema](#nestedblock--failover)) +- `load_balancer` (Block Set) Specifies the load balancing policy and configuration for services issuing requests to this upstream. (see [below for nested schema](#nestedblock--load_balancer)) +- `meta` (Map of String) Specifies key-value pairs to add to the KV store. +- `namespace` (String) Specifies the namespace that the service resolver applies to. +- `partition` (String) Specifies the admin partition that the service resolver applies to. +- `redirect` (Block Set) Specifies redirect instructions for local service traffic so that services deployed to a different network location resolve the upstream request instead. (see [below for nested schema](#nestedblock--redirect)) +- `request_timeout` (String) Specifies the timeout duration for receiving an HTTP response from this service. +- `subsets` (Block Set) Specifies names for custom service subsets and the conditions under which service instances belong to each subset. (see [below for nested schema](#nestedblock--subsets)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `failover` + +Required: + +- `subset_name` (String) Name of subset. + +Optional: + +- `datacenters` (List of String) Specifies an ordered list of datacenters at the failover location to attempt connections to during a failover scenario. When Consul cannot establish a connection with the first datacenter in the list, it proceeds sequentially until establishing a connection with another datacenter. +- `namespace` (String) Specifies the namespace at the failover location where the failover services are deployed. +- `sameness_group` (String) Specifies the sameness group at the failover location where the failover services are deployed. +- `service` (String) Specifies the name of the service to resolve at the failover location during a failover scenario. +- `service_subset` (String) Specifies the name of a subset of service instances to resolve at the failover location during a failover scenario. +- `targets` (Block List) Specifies a fixed list of failover targets to try during failover. This list can express complicated failover scenarios. (see [below for nested schema](#nestedblock--failover--targets)) + + +### Nested Schema for `failover.targets` + +Optional: + +- `datacenter` (String) Specifies the WAN federated datacenter to use for the failover target. If empty, the current datacenter is used. +- `namespace` (String) Specifies the namespace to use for the failover target. If empty, the default namespace is used. +- `partition` (String) Specifies the admin partition within the same datacenter to use for the failover target. If empty, the default partition is used. +- `peer` (String) Specifies the destination cluster peer to resolve the target service name from. +- `service` (String) Specifies the service name to use for the failover target. If empty, the current service name is used. +- `service_subset` (String) Specifies the named subset to use for the failover target. If empty, the default subset for the requested service name is used. + + + + +### Nested Schema for `load_balancer` + +Optional: + +- `hash_policies` (Block List) Specifies a list of hash policies to use for hashing load balancing algorithms. Consul evaluates hash policies individually and combines them so that identical lists result in the same hash. (see [below for nested schema](#nestedblock--load_balancer--hash_policies)) +- `least_request_config` (Block Set) Specifies configuration for the least_request policy type. (see [below for nested schema](#nestedblock--load_balancer--least_request_config)) +- `policy` (String) Specifies the type of load balancing policy for selecting a host. +- `ring_hash_config` (Block Set) Specifies configuration for the ring_hash policy type. (see [below for nested schema](#nestedblock--load_balancer--ring_hash_config)) + + +### Nested Schema for `load_balancer.hash_policies` + +Optional: + +- `cookie_config` (Block Set) Specifies additional configuration options for the cookie hash policy type. (see [below for nested schema](#nestedblock--load_balancer--hash_policies--cookie_config)) +- `field` (String) Specifies the attribute type to hash on. You cannot specify the Field parameter if SourceIP is also configured. +- `field_value` (String) Specifies the value to hash, such as a header name, cookie name, or a URL query parameter name. +- `source_ip` (Boolean) Determines if the hash type should be source IP address. +- `terminal` (Boolean) Determines if Consul should stop computing the hash when multiple hash policies are present. + + +### Nested Schema for `load_balancer.hash_policies.cookie_config` + +Optional: + +- `path` (String) Specifies the path to set for the cookie. +- `session` (Boolean) Directs Consul to generate a session cookie with no expiration. +- `ttl` (String) Specifies the TTL for generated cookies. Cannot be specified for session cookies. + + + + +### Nested Schema for `load_balancer.least_request_config` + +Optional: + +- `choice_count` (Number) + + + +### Nested Schema for `load_balancer.ring_hash_config` + +Optional: + +- `maximum_ring_size` (Number) Determines the maximum number of entries in the hash ring. +- `minimum_ring_size` (Number) Determines the minimum number of entries in the hash ring. + + + + +### Nested Schema for `redirect` + +Optional: + +- `datacenter` (String) Specifies the datacenter at the redirect’s destination that resolves local upstream requests. +- `namespace` (String) Specifies the namespace at the redirect’s destination that resolves local upstream requests. +- `partition` (String) Specifies the admin partition at the redirect’s destination that resolves local upstream requests. +- `peer` (String) Specifies the cluster with an active cluster peering connection at the redirect’s destination that resolves local upstream requests. +- `sameness_group` (String) Specifies the sameness group at the redirect’s destination that resolves local upstream requests. +- `service` (String) Specifies the name of a service at the redirect’s destination that resolves local upstream requests. +- `service_subset` (String) Specifies the name of a subset of services at the redirect’s destination that resolves local upstream requests. If empty, the default subset is used. If specified, you must also specify at least one of the following in the same Redirect map: Service, Namespace, andDatacenter. + + + +### Nested Schema for `subsets` + +Required: + +- `filter` (String) Specifies an expression that filters the DNS elements of service instances that belong to the subset. If empty, all healthy instances of a service are returned. +- `name` (String) Name of subset. +- `only_passing` (Boolean) Determines if instances that return a warning from a health check are allowed to resolve a request. When set to false, instances with passing and warning states are considered healthy. When set to true, only instances with a passing health check state are considered healthy. \ No newline at end of file From 30a5b9e829d204a6ed2368cb961345eb3224b0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Wed, 25 Oct 2023 12:42:29 +0200 Subject: [PATCH 5/5] Update documentation --- docs/resources/config_entry_service_resolver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/config_entry_service_resolver.md b/docs/resources/config_entry_service_resolver.md index a43cfe73..86985bc5 100644 --- a/docs/resources/config_entry_service_resolver.md +++ b/docs/resources/config_entry_service_resolver.md @@ -137,4 +137,4 @@ Required: - `filter` (String) Specifies an expression that filters the DNS elements of service instances that belong to the subset. If empty, all healthy instances of a service are returned. - `name` (String) Name of subset. -- `only_passing` (Boolean) Determines if instances that return a warning from a health check are allowed to resolve a request. When set to false, instances with passing and warning states are considered healthy. When set to true, only instances with a passing health check state are considered healthy. \ No newline at end of file +- `only_passing` (Boolean) Determines if instances that return a warning from a health check are allowed to resolve a request. When set to false, instances with passing and warning states are considered healthy. When set to true, only instances with a passing health check state are considered healthy.