diff --git a/.golangci.yml b/.golangci.yml index ee9028d50..279714be4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -22,6 +22,7 @@ issues: exclude-rules: - text: 'shadow: declaration of "(err|ctx)" shadows declaration at' linters: [ govet ] + exclude-dirs-use-default: false linters-settings: govet: @@ -78,7 +79,7 @@ linters-settings: - github.com/hashicorp/logutils - github.com/hashicorp/nomad/api - github.com/hashicorp/vault/api - - github.com/imdario/mergo + - dario.cat/mergo - github.com/mitchellh/go-homedir - github.com/mitchellh/hashstructure - github.com/mitchellh/mapstructure @@ -88,5 +89,4 @@ linters-settings: run: timeout: 10m - concurrency: 4 - skip-dirs-use-default: false \ No newline at end of file + concurrency: 4 \ No newline at end of file diff --git a/dependency/dependency.go b/dependency/dependency.go index 72ebb50c6..29ed744ad 100644 --- a/dependency/dependency.go +++ b/dependency/dependency.go @@ -75,18 +75,19 @@ type ServiceTags []string // client-agnostic, and the dependency determines which, if any, of the options // to use. type QueryOptions struct { - AllowStale bool - Datacenter string - Region string - Near string - Choose string - RequireConsistent bool - VaultGrace time.Duration - WaitIndex uint64 - WaitTime time.Duration - ConsulPeer string - ConsulPartition string - ConsulNamespace string + AllowStale bool + Datacenter string + Region string + Near string + Choose string + RequireConsistent bool + VaultGrace time.Duration + WaitIndex uint64 + WaitTime time.Duration + ConsulPeer string + ConsulPartition string + ConsulNamespace string + ConsulSamenessGroup string } func (q *QueryOptions) Merge(o *QueryOptions) *QueryOptions { @@ -150,6 +151,10 @@ func (q *QueryOptions) Merge(o *QueryOptions) *QueryOptions { r.ConsulPeer = o.ConsulPeer } + if o.ConsulSamenessGroup != "" { + r.ConsulSamenessGroup = o.ConsulSamenessGroup + } + return &r } @@ -159,6 +164,7 @@ func (q *QueryOptions) ToConsulOpts() *consulapi.QueryOptions { Datacenter: q.Datacenter, Namespace: q.ConsulNamespace, Partition: q.ConsulPartition, + SamenessGroup: q.ConsulSamenessGroup, Peer: q.ConsulPeer, Near: q.Near, RequireConsistent: q.RequireConsistent, @@ -184,10 +190,11 @@ func GetConsulQueryOpts(queryMap map[string]string, endpointLabel string) (url.V switch key { case QueryNamespace, QueryPeer, - QueryPartition: + QueryPartition, + QuerySamenessGroup: default: return nil, - fmt.Errorf("%s: invalid query parameter key %q in query %q: supported keys: %s,%s,%s", endpointLabel, key, queryRaw, QueryNamespace, QueryPeer, QueryPartition) + fmt.Errorf("%s: invalid query parameter key %q in query %q: supported keys: %s,%s,%s,%s", endpointLabel, key, queryRaw, QueryNamespace, QueryPeer, QueryPartition, QuerySamenessGroup) } } } diff --git a/dependency/dependency_test.go b/dependency/dependency_test.go index 210f7c6eb..f5acf1c91 100644 --- a/dependency/dependency_test.go +++ b/dependency/dependency_test.go @@ -61,15 +61,11 @@ func TestMain(m *testing.M) { Address: testConsul.HTTPAddr, }); err != nil { testConsul.Stop() - testVault.Stop() - testNomad.Stop() Fatalf("failed to create consul client: %v\n", err) } if t, err := test.NewTenancyHelper(clients.Consul()); err != nil { - testConsul.Stop() - testVault.Stop() - testNomad.Stop() + stopTestClients() Fatalf("failed to create tenancy helper: %v\n", err) } else { tenancyHelper = t @@ -86,52 +82,46 @@ func TestMain(m *testing.M) { if err := clients.CreateNomadClient(&CreateNomadClientInput{ Address: "http://127.0.0.1:4646", }); err != nil { - testConsul.Stop() - testVault.Stop() - testNomad.Stop() + stopTestClients() Fatalf("failed to create nomad client: %v\n", err) } testClients = clients if err := testClients.createConsulPartitions(); err != nil { - testConsul.Stop() - testVault.Stop() - testNomad.Stop() + stopTestClients() Fatalf("failed to create consul partitions: %v\n", err) } if err := testClients.createConsulNs(); err != nil { - testConsul.Stop() - testVault.Stop() - testNomad.Stop() + stopTestClients() Fatalf("failed to create consul namespaces: %v\n", err) } setupVaultPKI(clients) if err := testClients.createConsulTestResources(); err != nil { - testConsul.Stop() - testVault.Stop() - testNomad.Stop() + stopTestClients() Fatalf("failed to create consul test resources: %v\n", err) } // Wait for Nomad initialization to finish if err := <-nomadFuture; err != nil { - testConsul.Stop() - testNomad.Stop() - testVault.Stop() + stopTestClients() Fatalf("failed to start Nomad: %v\n", err) } exit := m.Run() tb.DoCleanup() + stopTestClients() + os.Exit(exit) +} + +func stopTestClients() { testConsul.Stop() testVault.Stop() testNomad.Stop() - os.Exit(exit) } func (c *ClientSet) createConsulTestResources() error { @@ -239,6 +229,27 @@ func (c *ClientSet) createConsulTestResources() error { return nil } +func (c *ClientSet) createConsulSamenessGroups(name, partition, failoverPartition string) error { + members := append([]api.SamenessGroupMember{}, api.SamenessGroupMember{ + Partition: partition, + }, api.SamenessGroupMember{ + Partition: failoverPartition, + }) + sg := &api.SamenessGroupConfigEntry{ + Kind: api.SamenessGroup, + Name: name, + Partition: partition, + DefaultForFailover: true, + Members: members, + } + _, _, err := c.consul.client.ConfigEntries().Set(sg, &api.WriteOptions{}) + if err != nil { + return err + } + + return nil +} + func (c *ClientSet) createConsulPeerings(tenancy *test.Tenancy) error { generateReq := api.PeeringGenerateTokenRequest{PeerName: "foo", Partition: tenancy.Partition} _, _, err := c.consul.client.Peerings().GenerateToken(context.Background(), generateReq, &api.WriteOptions{}) @@ -545,8 +556,7 @@ func (v *nomadServer) DeleteVariable(path string, opts *nomadapi.WriteOptions) e func (c *ClientSet) createConsulPartitions() error { for p := range tenancyHelper.GetUniquePartitions() { if p.Name != "" && p.Name != "default" { - partition := &api.Partition{Name: p.Name} - _, _, err := c.Consul().Partitions().Create(context.Background(), partition, nil) + err := c.createConsulPartition(p.Name) if err != nil { return err } @@ -556,6 +566,15 @@ func (c *ClientSet) createConsulPartitions() error { return nil } +func (c *ClientSet) createConsulPartition(name string) error { + partition := &api.Partition{Name: name} + _, _, err := c.Consul().Partitions().Create(context.Background(), partition, nil) + if err != nil { + return err + } + return nil +} + func (c *ClientSet) createConsulNs() error { for _, tenancy := range tenancyHelper.TestTenancies() { if tenancy.Namespace != "" && tenancy.Namespace != "default" { diff --git a/dependency/health_service.go b/dependency/health_service.go index 7728615dd..7c4517a57 100644 --- a/dependency/health_service.go +++ b/dependency/health_service.go @@ -23,9 +23,10 @@ const ( HealthCritical = "critical" HealthMaint = "maintenance" - QueryNamespace = "ns" - QueryPartition = "partition" - QueryPeer = "peer" + QueryNamespace = "ns" + QueryPartition = "partition" + QueryPeer = "peer" + QuerySamenessGroup = "sameness-group" NodeMaint = "_node_maintenance" ServiceMaint = "_service_maintenance:" @@ -66,15 +67,16 @@ type HealthService struct { type HealthServiceQuery struct { stopCh chan struct{} - dc string - filters []string - name string - near string - tag string - connect bool - partition string - peer string - namespace string + dc string + filters []string + name string + near string + tag string + connect bool + partition string + peer string + namespace string + samenessGroup string } // NewHealthServiceQuery processes the strings to build a service dependency. @@ -122,18 +124,25 @@ func healthServiceQuery(s string, connect bool) (*HealthServiceQuery, error) { return nil, err } - return &HealthServiceQuery{ - stopCh: make(chan struct{}, 1), - dc: m["dc"], - filters: filters, - name: m["name"], - near: m["near"], - tag: m["tag"], - connect: connect, - namespace: queryParams.Get(QueryNamespace), - peer: queryParams.Get(QueryPeer), - partition: queryParams.Get(QueryPartition), - }, nil + if queryParams.Get(QuerySamenessGroup) != "" && queryParams.Get(QueryPeer) != "" { + return nil, fmt.Errorf("health.service: cannot specify both %s and %s", QueryPeer, QuerySamenessGroup) + } + + qry := &HealthServiceQuery{ + stopCh: make(chan struct{}, 1), + dc: m["dc"], + filters: filters, + name: m["name"], + near: m["near"], + tag: m["tag"], + connect: connect, + namespace: queryParams.Get(QueryNamespace), + peer: queryParams.Get(QueryPeer), + partition: queryParams.Get(QueryPartition), + samenessGroup: queryParams.Get(QuerySamenessGroup), + } + + return qry, nil } // Fetch queries the Consul API defined by the given client and returns a slice @@ -146,11 +155,12 @@ func (d *HealthServiceQuery) Fetch(clients *ClientSet, opts *QueryOptions) (inte } opts = opts.Merge(&QueryOptions{ - Datacenter: d.dc, - Near: d.near, - ConsulNamespace: d.namespace, - ConsulPartition: d.partition, - ConsulPeer: d.peer, + Datacenter: d.dc, + Near: d.near, + ConsulNamespace: d.namespace, + ConsulPartition: d.partition, + ConsulPeer: d.peer, + ConsulSamenessGroup: d.samenessGroup, }) u := &url.URL{ @@ -261,6 +271,9 @@ func (d *HealthServiceQuery) String() string { if d.peer != "" { name = name + "@peer=" + d.peer } + if d.samenessGroup != "" { + name = name + "@sameness-group=" + d.samenessGroup + } if d.near != "" { name = name + "~" + d.near } diff --git a/dependency/health_service_test.go b/dependency/health_service_test.go index a5d1bd02d..fb6c1cf65 100644 --- a/dependency/health_service_test.go +++ b/dependency/health_service_test.go @@ -5,8 +5,10 @@ package dependency import ( "fmt" + "github.com/stretchr/testify/require" "reflect" "testing" + "time" "unsafe" "github.com/hashicorp/consul-template/test" @@ -20,7 +22,7 @@ func TestNewHealthServiceQuery(t *testing.T) { name string i string exp *HealthServiceQuery - err bool + err error } cases := tenancyHelper.GenerateTenancyTests(func(tenancy *test.Tenancy) []interface{} { return []interface{}{ @@ -28,37 +30,37 @@ func TestNewHealthServiceQuery(t *testing.T) { tenancyHelper.AppendTenancyInfo("empty", tenancy), "", nil, - true, + fmt.Errorf(`health.service: invalid format: ""`), }, testCase{ tenancyHelper.AppendTenancyInfo("dc_only", tenancy), "@dc1", nil, - true, + fmt.Errorf(`health.service: invalid format: "@dc1"`), }, testCase{ tenancyHelper.AppendTenancyInfo("near_only", tenancy), "~near", nil, - true, + fmt.Errorf(`health.service: invalid format: "~near"`), }, testCase{ tenancyHelper.AppendTenancyInfo("tag_only", tenancy), "tag.", nil, - true, + fmt.Errorf(`health.service: invalid format: "tag."`), }, testCase{ tenancyHelper.AppendTenancyInfo("query_only", tenancy), fmt.Sprintf("?ns=%s", tenancy.Namespace), nil, - true, + fmt.Errorf(`health.service: invalid format: "?ns=%s"`, tenancy.Namespace), }, testCase{ tenancyHelper.AppendTenancyInfo("invalid query param (unsupported key)", tenancy), "name?unsupported=test", nil, - true, + fmt.Errorf(`health.service: invalid query parameter key "unsupported" in query "unsupported=test": supported keys: ns,peer,partition,sameness-group`), }, testCase{ tenancyHelper.AppendTenancyInfo("name", tenancy), @@ -67,7 +69,7 @@ func TestNewHealthServiceQuery(t *testing.T) { filters: []string{"passing"}, name: "name", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("name_dc", tenancy), @@ -77,7 +79,7 @@ func TestNewHealthServiceQuery(t *testing.T) { filters: []string{"passing"}, name: "name", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("name_dc_near", tenancy), @@ -88,7 +90,7 @@ func TestNewHealthServiceQuery(t *testing.T) { name: "name", near: "near", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("name_near", tenancy), @@ -98,7 +100,7 @@ func TestNewHealthServiceQuery(t *testing.T) { name: "name", near: "near", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("tag_name", tenancy), @@ -108,7 +110,7 @@ func TestNewHealthServiceQuery(t *testing.T) { name: "name", tag: "tag", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("tag_name_dc", tenancy), @@ -119,7 +121,7 @@ func TestNewHealthServiceQuery(t *testing.T) { name: "name", tag: "tag", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("tag_name_near", tenancy), @@ -130,7 +132,7 @@ func TestNewHealthServiceQuery(t *testing.T) { near: "near", tag: "tag", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("tag_name_dc_near", tenancy), @@ -142,7 +144,7 @@ func TestNewHealthServiceQuery(t *testing.T) { near: "near", tag: "tag", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("name_partition", tenancy), @@ -152,7 +154,7 @@ func TestNewHealthServiceQuery(t *testing.T) { name: "name", partition: tenancy.Partition, }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("name_peer", tenancy), @@ -162,7 +164,7 @@ func TestNewHealthServiceQuery(t *testing.T) { name: "name", peer: "foo", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("name_ns", tenancy), @@ -172,7 +174,7 @@ func TestNewHealthServiceQuery(t *testing.T) { name: "name", namespace: tenancy.Namespace, }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("name_ns_peer_partition", tenancy), @@ -184,7 +186,23 @@ func TestNewHealthServiceQuery(t *testing.T) { peer: "bar", partition: tenancy.Partition, }, - false, + nil, + }, + testCase{ + tenancyHelper.AppendTenancyInfo("name_samenessgroup", tenancy), + "name?sameness-group=sg1", + &HealthServiceQuery{ + filters: []string{"passing"}, + name: "name", + samenessGroup: "sg1", + }, + nil, + }, + testCase{ + tenancyHelper.AppendTenancyInfo("samenessgroup and peer should return error if both set", tenancy), + "name?sameness-group=sg1&peer=peer1", + nil, + fmt.Errorf("health.service: cannot specify both peer and sameness-group"), }, testCase{ tenancyHelper.AppendTenancyInfo("namespace set twice should use first", tenancy), @@ -194,7 +212,7 @@ func TestNewHealthServiceQuery(t *testing.T) { name: "name", namespace: tenancy.Namespace, }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("empty value in query param", tenancy), @@ -206,7 +224,7 @@ func TestNewHealthServiceQuery(t *testing.T) { peer: "", partition: "", }, - false, + nil, }, testCase{ tenancyHelper.AppendTenancyInfo("query with other parameters", tenancy), @@ -221,7 +239,7 @@ func TestNewHealthServiceQuery(t *testing.T) { namespace: tenancy.Namespace, partition: tenancy.Partition, }, - false, + nil, }, } }) @@ -230,9 +248,7 @@ func TestNewHealthServiceQuery(t *testing.T) { tc := test.(testCase) t.Run(fmt.Sprintf("%d_%s", i, tc.name), func(t *testing.T) { act, err := NewHealthServiceQuery(tc.i) - if (err != nil) != tc.err { - t.Fatal(err) - } + assert.Equal(t, tc.err, err) if act != nil { act.stopCh = nil @@ -675,6 +691,143 @@ func TestHealthServiceQuery_Fetch(t *testing.T) { } } +// TestHealthServiceQuery_Fetch_SamenessGroup ensures that consul-template re-runs when the blocking query updates. +// The different behaviors of the blocking query, e.g. "when a service becomes unhealthy it should failover, +// when it becomes healthy again it should fail back", are tested in the Consul codebase. +func TestHealthServiceQuery_Fetch_SamenessGroup(t *testing.T) { + if !tenancyHelper.IsConsulEnterprise() { + t.Skip("Enterprise only test") + } + // Arrange - set up test data + catalog := testClients.Consul().Catalog() + + partitionOne := "sg-partition-1" + partitionTwo := "sg-partition-2" + nodeOne := "node" + partitionOne + nodeTwo := "node" + partitionTwo + samenessGroup := "test-sameness-group" + + require.NoError(t, testClients.createConsulPartition(partitionOne)) + require.NoError(t, testClients.createConsulPartition(partitionTwo)) + require.NoError(t, testClients.createConsulSamenessGroups(samenessGroup, partitionOne, partitionTwo)) + + // Register services with the same name in partionOne and partitionTwo and them to a sameness group so that we can test failover. + svcName := "sameness-group-service" + registerSvc := func(service, node, partition, status string) { + checkName := fmt.Sprintf("%s:%s", service, node) + svcRegistration := &api.CatalogRegistration{ + Service: &api.AgentService{ + ID: service, + Service: service, + Port: 12345, + Partition: partition, + Namespace: "default", + Connect: &api.AgentServiceConnect{}, + }, + Partition: partition, + Node: node, + Address: "127.0.0.1", + Checks: api.HealthChecks{ + &api.HealthCheck{ + Node: node, + CheckID: checkName, + Name: checkName, + Status: status, + ServiceID: svcName, + ServiceName: svcName, + }, + }, + } + + _, err := catalog.Register(svcRegistration, nil) + if err != nil { + t.Fatal(err) + } + } + + // set up service in each partition + registerSvc(svcName, nodeOne, partitionOne, "passing") + registerSvc(svcName, nodeTwo, partitionTwo, "passing") + + // Act - fetch the service + query := fmt.Sprintf("%s?sameness-group=%s&partition=%s", svcName, samenessGroup, partitionOne) + d, err := NewHealthServiceQuery(query) + if err != nil { + t.Fatal(err) + } + + svcs, meta, err := d.Fetch(testClients, nil) + if err != nil { + t.Fatal(err) + } + + // Assert that the service instance returned is from the first listed partition. + // This is expected because the service is healthy so it shouldn't failover + // to other partitions. + healthServices := svcs.([]*HealthService) + require.True(t, len(healthServices) == 1) + require.Equal(t, "node"+partitionOne, healthServices[0].Node) + + // set up blocking query with last index + dataCh := make(chan interface{}, 1) + errCh := make(chan error, 1) + go func() { + data, _, err := d.Fetch(testClients, &QueryOptions{WaitIndex: meta.LastIndex}) + if err != nil { + errCh <- err + return + } + dataCh <- data + }() + + // update partition one to initiate failover + registerSvc(svcName, nodeOne, partitionOne, "critical") + + select { + case err := <-errCh: + if err != ErrStopped { + t.Fatal(err) + } + case <-time.After(1 * time.Minute): + t.Errorf("did not stop") + case val := <-dataCh: + if val != nil { + for _, v := range val.([]*HealthService) { + v.NodeID = "" + v.Checks = nil + // delete any version data from ServiceMeta + v.ServiceMeta = filterVersionMeta(v.ServiceMeta) + v.NodeTaggedAddresses = filterAddresses( + v.NodeTaggedAddresses) + v.NodeMeta = filterVersionMeta(v.NodeMeta) + } + } + + // Assert - verify the results + expectedResult := []*HealthService{ + { + // The instance should now be from the failover partition. + Node: nodeTwo, + NodeAddress: testConsul.Config.Bind, + ServiceMeta: map[string]string{}, + Address: testConsul.Config.Bind, + NodeTaggedAddresses: map[string]string{}, + NodeMeta: map[string]string{}, + Port: 12345, + ID: svcName, + Name: svcName, + Tags: []string{}, + Status: HealthPassing, + Weights: api.AgentWeights{ + Passing: 1, + Warning: 1, + }, + }, + } + assert.Equal(t, expectedResult, val) + } +} + func TestHealthServiceQuery_String(t *testing.T) { type testCase struct { name string diff --git a/docs/templating-language.md b/docs/templating-language.md index 4892e5489..387bcb976 100644 --- a/docs/templating-language.md +++ b/docs/templating-language.md @@ -775,12 +775,29 @@ Query [Consul][consul] for services based on their health. The `` attribute is optional; if omitted, all nodes will be queried. -The `` attribute is optional; if omitted, the `default` Consul namespace, `default` partition will be queried. `` can be used to set the Consul [namespace](https://developer.hashicorp.com/consul/api-docs/health#ns-2), partition, or [peer](https://developer.hashicorp.com/consul/api-docs/health#peer). `` accepts a url query-parameter format, e.g.: +The `` attribute is optional; if omitted, the `default` Consul namespace, `default` partition will be queried. `` can be used to set the Consul [namespace](https://developer.hashicorp.com/consul/api-docs/health#ns-2), partition, sameness group, or [peer](https://developer.hashicorp.com/consul/api-docs/health#peer). `` accepts a url query-parameter format, e.g.: ```golang {{ service "service-name?ns=namespace-name&peer=peer-name&partition=partition-name" }} ``` +When using the `sameness-group` query parameter, the following rules are applied to use with other query parameters: +- `partition` is used to denote where the Sameness Group Config Entry is stored. +- `ns` is ignored. +- Either `peer` or `partition` can be used, but not both. + +```golang +{{ service "service-name?sameness-group=sameness-group-name" }} +``` +When using the `sameness-group` query parameter, the result will be based on the following rules: +- The Sameness Group Config Entry will be retrieved using the `partition` and the `sameness-group` parameters. +- The `` attribute is optional; if omitted, the local datacenter is used. diff --git a/go.mod b/go.mod index 89cba799d..2fbb488f0 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ toolchain go1.22.4 require ( github.com/BurntSushi/toml v1.3.2 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc - github.com/hashicorp/consul/api v1.28.3 - github.com/hashicorp/consul/sdk v0.15.0 + github.com/hashicorp/consul/api v1.29.1 + github.com/hashicorp/consul/sdk v0.16.1 github.com/hashicorp/go-gatedio v0.5.0 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-multierror v1.1.1 @@ -20,7 +20,6 @@ require ( github.com/hashicorp/nomad/api v0.0.0-20230103221135-ce00d683f9be github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/vault/api v1.10.0 - github.com/imdario/mergo v0.3.13 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/hashstructure v1.1.0 github.com/mitchellh/mapstructure v1.5.0 @@ -32,6 +31,7 @@ require ( ) require ( + dario.cat/mergo v1.0.0 github.com/Masterminds/sprig/v3 v3.2.3 github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 @@ -47,7 +47,6 @@ require ( github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/hashicorp/consul/proto-public v0.6.1 // indirect github.com/hashicorp/cronexpr v1.1.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -59,6 +58,7 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.11 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/miekg/dns v1.1.50 // indirect diff --git a/go.sum b/go.sum index f73590890..6aed2eb3c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -72,12 +74,12 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/consul/api v1.28.3 h1:IE06LST/knnCQ+cxcvzyXRF/DetkgGhJoaOFd4l9xkk= -github.com/hashicorp/consul/api v1.28.3/go.mod h1:7AGcUFu28HkgOKD/GmsIGIFzRTmN0L02AE9Thsr2OhU= +github.com/hashicorp/consul/api v1.29.1 h1:UEwOjYJrd3lG1x5w7HxDRMGiAUPrb3f103EoeKuuEcc= +github.com/hashicorp/consul/api v1.29.1/go.mod h1:lumfRkY/coLuqMICkI7Fh3ylMG31mQSRZyef2c5YvJI= github.com/hashicorp/consul/proto-public v0.6.1 h1:+uzH3olCrksXYWAYHKqK782CtK9scfqH+Unlw3UHhCg= github.com/hashicorp/consul/proto-public v0.6.1/go.mod h1:cXXbOg74KBNGajC+o8RlA502Esf0R9prcoJgiOX/2Tg= -github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= -github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= +github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s= github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -148,9 +150,8 @@ github.com/hashicorp/vault/api/auth/kubernetes v0.5.0/go.mod h1:afrElBIO9Q4sHFVu github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -392,6 +393,5 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/template/funcs.go b/template/funcs.go index c8066f785..d3270cc11 100644 --- a/template/funcs.go +++ b/template/funcs.go @@ -25,11 +25,11 @@ import ( "text/template" "time" + "dario.cat/mergo" "github.com/BurntSushi/toml" spewLib "github.com/davecgh/go-spew/spew" "github.com/hashicorp/consul/api" socktmpl "github.com/hashicorp/go-sockaddr/template" - "github.com/imdario/mergo" "github.com/pkg/errors" "golang.org/x/text/cases" "golang.org/x/text/language" diff --git a/test/helpers.go b/test/helpers.go index d056f3017..f270a4d49 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -113,3 +113,11 @@ func (t *TestingTB) Cleanup(f func()) { } } } +func (*TestingTB) Error(...any) {} +func (*TestingTB) Errorf(string, ...any) {} +func (*TestingTB) Fail() {} +func (*TestingTB) FailNow() {} +func (*TestingTB) Fatal(...any) {} +func (*TestingTB) Log(...any) {} +func (*TestingTB) Setenv(string, string) {} +func (*TestingTB) TempDir() string { return "" }