diff --git a/dependency/catalog_datacenters.go b/dependency/catalog_datacenters.go index 55d72427c..e99767e08 100644 --- a/dependency/catalog_datacenters.go +++ b/dependency/catalog_datacenters.go @@ -19,7 +19,7 @@ var ( // CatalogDatacentersQuerySleepTime is the amount of time to sleep between // queries, since the endpoint does not support blocking queries. - CatalogDatacentersQuerySleepTime = 15 * time.Second + CatalogDatacentersQuerySleepTime = DefaultNonBlockingQuerySleepTime ) // CatalogDatacentersQuery is the dependency to query all datacenters diff --git a/dependency/catalog_services.go b/dependency/catalog_services.go index 205b00fda..81307d0a0 100644 --- a/dependency/catalog_services.go +++ b/dependency/catalog_services.go @@ -18,7 +18,7 @@ var ( // Ensure implements _ Dependency = (*CatalogServicesQuery)(nil) - // CatalogServicesQueryRe is the regular expression to use for CatalogNodesQuery. + // CatalogServicesQueryRe is the regular expression to use for CatalogServicesQuery. CatalogServicesQueryRe = regexp.MustCompile(`\A` + queryRe + dcRe + `\z`) ) diff --git a/dependency/consul_exported_services.go b/dependency/consul_exported_services.go new file mode 100644 index 000000000..0984528da --- /dev/null +++ b/dependency/consul_exported_services.go @@ -0,0 +1,141 @@ +package dependency + +import ( + "fmt" + "log" + "net/url" + "slices" + + capi "github.com/hashicorp/consul/api" +) + +const exportedServicesEndpointLabel = "list.exportedServices" + +// Ensure implements +var _ Dependency = (*ListExportedServicesQuery)(nil) + +// ListExportedServicesQuery is the representation of a requested exported services +// dependency from inside a template. +type ListExportedServicesQuery struct { + stopCh chan struct{} + partition string +} + +type ExportedService struct { + // Name of the service + Service string + + // Partition of the service + Partition string + + // Namespace of the service + Namespace string + + // Consumers is a list of downstream consumers of the service. + Consumers ResolvedConsumers +} + +type ResolvedConsumers struct { + Peers []string + Partitions []string + SamenessGroups []string +} + +func fromConsulExportedService(svc capi.ExportedService) ExportedService { + peers := make([]string, 0, len(svc.Consumers)) + partitions := make([]string, 0, len(svc.Consumers)) + samenessGroups := make([]string, 0, len(svc.Consumers)) + for _, consumer := range svc.Consumers { + if consumer.Peer != "" { + peers = append(peers, consumer.Peer) + } + if consumer.Partition != "" { + partitions = append(partitions, consumer.Partition) + } + if consumer.SamenessGroup != "" { + samenessGroups = append(samenessGroups, consumer.SamenessGroup) + } + } + + return ExportedService{ + Service: svc.Name, + Consumers: ResolvedConsumers{ + Peers: peers, + Partitions: partitions, + SamenessGroups: samenessGroups, + }, + } +} + +// NewListExportedServicesQuery parses a string of the format @dc. +func NewListExportedServicesQuery(s string) (*ListExportedServicesQuery, error) { + return &ListExportedServicesQuery{ + stopCh: make(chan struct{}), + partition: s, + }, nil +} + +func (c *ListExportedServicesQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) { + select { + case <-c.stopCh: + return nil, nil, ErrStopped + default: + } + + opts = opts.Merge(&QueryOptions{ + ConsulPartition: c.partition, + }) + + log.Printf("[TRACE] %s: GET %s", c, &url.URL{ + Path: "/v1/config/exported-services", + RawQuery: opts.String(), + }) + + consulExportedServices, qm, err := clients.Consul().ConfigEntries().List(capi.ExportedServices, opts.ToConsulOpts()) + if err != nil { + return nil, nil, fmt.Errorf("%s: %w", c.String(), err) + } + + exportedServices := make([]ExportedService, 0, len(consulExportedServices)) + for _, cfgEntry := range consulExportedServices { + svc := cfgEntry.(*capi.ExportedServicesConfigEntry) + for _, svc := range svc.Services { + exportedServices = append(exportedServices, fromConsulExportedService(svc)) + } + } + + log.Printf("[TRACE] %s: returned %d results", c, len(exportedServices)) + + slices.SortStableFunc(exportedServices, func(i, j ExportedService) int { + if i.Service < j.Service { + return -1 + } else if i.Service > j.Service { + return 1 + } + return 0 + }) + + rm := &ResponseMetadata{ + LastContact: qm.LastContact, + LastIndex: qm.LastIndex, + } + + return exportedServices, rm, nil +} + +// CanShare returns if this dependency is shareable when consul-template is running in de-duplication mode. +func (c *ListExportedServicesQuery) CanShare() bool { + return true +} + +func (c *ListExportedServicesQuery) String() string { + return exportedServicesEndpointLabel +} + +func (c *ListExportedServicesQuery) Stop() { + close(c.stopCh) +} + +func (c *ListExportedServicesQuery) Type() Type { + return TypeConsul +} diff --git a/dependency/consul_exported_services_test.go b/dependency/consul_exported_services_test.go new file mode 100644 index 000000000..d9295c546 --- /dev/null +++ b/dependency/consul_exported_services_test.go @@ -0,0 +1,251 @@ +package dependency + +import ( + "testing" + + "github.com/stretchr/testify/require" + + capi "github.com/hashicorp/consul/api" +) + +func TestListExportedServicesQuery_Fetch(t *testing.T) { + testCases := map[string]struct { + partition string + skipIfNonEnterprise bool + exportedServices *capi.ExportedServicesConfigEntry + expected []ExportedService + }{ + "no services": { + partition: defaultOrEmtpyString(), + exportedServices: nil, + expected: []ExportedService{}, + }, + "default partition - one exported service - partitions set": { + partition: "default", + skipIfNonEnterprise: !tenancyHelper.IsConsulEnterprise(), + exportedServices: &capi.ExportedServicesConfigEntry{ + Name: "default", + Partition: "default", + Services: []capi.ExportedService{ + { + Name: "service1", + Consumers: []capi.ServiceConsumer{ + { + Partition: "foo", + }, + }, + }, + }, + }, + expected: []ExportedService{ + { + Service: "service1", + Consumers: ResolvedConsumers{ + Peers: []string{}, + Partitions: []string{"foo"}, + SamenessGroups: []string{}, + }, + }, + }, + }, + "default partition - multiple exported services - partitions set": { + partition: "default", + skipIfNonEnterprise: !tenancyHelper.IsConsulEnterprise(), + exportedServices: &capi.ExportedServicesConfigEntry{ + Name: "default", + Partition: "default", + Services: []capi.ExportedService{ + { + Name: "service1", + Consumers: []capi.ServiceConsumer{ + { + Partition: "foo", + }, + }, + }, + { + Name: "service2", + Consumers: []capi.ServiceConsumer{ + { + Partition: "foo", + }, + }, + }, + }, + }, + expected: []ExportedService{ + { + Service: "service1", + Consumers: ResolvedConsumers{ + Peers: []string{}, + Partitions: []string{"foo"}, + SamenessGroups: []string{}, + }, + }, + { + Service: "service2", + Consumers: ResolvedConsumers{ + Peers: []string{}, + Partitions: []string{"foo"}, + SamenessGroups: []string{}, + }, + }, + }, + }, + "non default partition - multiple exported services - partitions set": { + partition: "foo", + skipIfNonEnterprise: !tenancyHelper.IsConsulEnterprise(), + exportedServices: &capi.ExportedServicesConfigEntry{ + Name: "foo", + Partition: "foo", + Services: []capi.ExportedService{ + { + Name: "service1", + Consumers: []capi.ServiceConsumer{ + { + Partition: "default", + }, + }, + }, + { + Name: "service2", + Consumers: []capi.ServiceConsumer{ + { + Partition: "default", + }, + }, + }, + }, + }, + expected: []ExportedService{ + { + Service: "service1", + Consumers: ResolvedConsumers{ + Peers: []string{}, + Partitions: []string{"default"}, + SamenessGroups: []string{}, + }, + }, + { + Service: "service2", + Consumers: ResolvedConsumers{ + Peers: []string{}, + Partitions: []string{"default"}, + SamenessGroups: []string{}, + }, + }, + }, + }, + "default partition - one exported service - peers set": { + partition: defaultOrEmtpyString(), + skipIfNonEnterprise: false, + exportedServices: &capi.ExportedServicesConfigEntry{ + Name: "default", + Partition: defaultOrEmtpyString(), + Services: []capi.ExportedService{ + { + Name: "service1", + Consumers: []capi.ServiceConsumer{ + { + Peer: "another", + }, + }, + }, + }, + }, + expected: []ExportedService{ + { + Service: "service1", + Consumers: ResolvedConsumers{ + Peers: []string{"another"}, + Partitions: []string{}, + SamenessGroups: []string{}, + }, + }, + }, + }, + "default partition - multiple exported services - peers set": { + partition: defaultOrEmtpyString(), + skipIfNonEnterprise: false, + exportedServices: &capi.ExportedServicesConfigEntry{ + Name: "default", + Partition: defaultOrEmtpyString(), + Services: []capi.ExportedService{ + { + Name: "service1", + Consumers: []capi.ServiceConsumer{ + { + Peer: "another", + }, + }, + }, + { + Name: "service2", + Consumers: []capi.ServiceConsumer{ + { + Peer: "another", + }, + }, + }, + }, + }, + expected: []ExportedService{ + { + Service: "service1", + Consumers: ResolvedConsumers{ + Peers: []string{"another"}, + Partitions: []string{}, + SamenessGroups: []string{}, + }, + }, + { + Service: "service2", + Consumers: ResolvedConsumers{ + Peers: []string{"another"}, + Partitions: []string{}, + SamenessGroups: []string{}, + }, + }, + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + if tc.skipIfNonEnterprise { + t.Skipf("skipping test %q as Consul is not enterprise", name) + } + + opts := &capi.WriteOptions{Partition: tc.partition} + + if tc.exportedServices != nil { + _, _, err := testClients.Consul().ConfigEntries().Set(tc.exportedServices, opts) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + + q, err := NewListExportedServicesQuery(tc.partition) + require.NoError(t, err) + + actual, _, err := q.Fetch(testClients, nil) + require.NoError(t, err) + + require.ElementsMatch(t, tc.expected, actual) + + if tc.exportedServices != nil { + // need to clean up because we use a single shared consul instance + _, err = testClients.Consul().ConfigEntries().Delete(capi.ExportedServices, tc.exportedServices.Name, opts) + require.NoError(t, err) + } + }) + } +} + +func defaultOrEmtpyString() string { + if tenancyHelper.IsConsulEnterprise() { + return "default" + } + + return "" +} diff --git a/dependency/consul_partitions.go b/dependency/consul_partitions.go new file mode 100644 index 000000000..d4095e806 --- /dev/null +++ b/dependency/consul_partitions.go @@ -0,0 +1,114 @@ +package dependency + +import ( + "context" + "fmt" + "log" + "net/url" + "slices" + "strings" + "time" + + "github.com/hashicorp/consul/api" +) + +// Ensure implements +var ( + _ Dependency = (*ListPartitionsQuery)(nil) + + // ListPartitionsQuerySleepTime is the amount of time to sleep between + // queries, since the endpoint does not support blocking queries. + ListPartitionsQuerySleepTime = DefaultNonBlockingQuerySleepTime +) + +// Partition is a partition in Consul. +type Partition struct { + Name string + Description string +} + +// ListPartitionsQuery is the representation of a requested partitions +// dependency from inside a template. +type ListPartitionsQuery struct { + stopCh chan struct{} +} + +func NewListPartitionsQuery() (*ListPartitionsQuery, error) { + return &ListPartitionsQuery{ + stopCh: make(chan struct{}, 1), + }, nil +} + +func (c *ListPartitionsQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) { + opts = opts.Merge(&QueryOptions{}) + + log.Printf("[TRACE] %s: GET %s", c, &url.URL{ + Path: "/v1/partitions", + RawQuery: opts.String(), + }) + + // This is certainly not elegant, but the partitions endpoint does not support + // blocking queries, so we are going to "fake it until we make it". When we + // first query, the LastIndex will be "0", meaning we should immediately + // return data, but future calls will include a LastIndex. If we have a + // LastIndex in the query metadata, sleep for 15 seconds before asking Consul + // again. + // + // This is probably okay given the frequency in which partitions actually + // change, but is technically not edge-triggering. + if opts.WaitIndex != 0 { + log.Printf("[TRACE] %s: long polling for %s", c, ListPartitionsQuerySleepTime) + + select { + case <-c.stopCh: + return nil, nil, ErrStopped + case <-time.After(ListPartitionsQuerySleepTime): + } + } + + partitions, _, err := clients.Consul().Partitions().List(context.Background(), opts.ToConsulOpts()) + if err != nil { + if strings.Contains(err.Error(), "Invalid URL path") { + return nil, nil, fmt.Errorf("%s: Partitions are an enterprise feature: %w", c.String(), err) + } + + return nil, nil, fmt.Errorf("%s: %w", c.String(), err) + } + + log.Printf("[TRACE] %s: returned %d results", c, len(partitions)) + + slices.SortFunc(partitions, func(i, j *api.Partition) int { + return strings.Compare(i.Name, j.Name) + }) + + resp := []*Partition{} + for _, partition := range partitions { + if partition != nil { + resp = append(resp, &Partition{ + Name: partition.Name, + Description: partition.Description, + }) + } + } + + // Use respWithMetadata which always increments LastIndex and results + // in fetching new data for endpoints that don't support blocking queries + return respWithMetadata(resp) +} + +// CanShare returns if this dependency is shareable when consul-template is running in de-duplication mode. +func (c *ListPartitionsQuery) CanShare() bool { + return true +} + +func (c *ListPartitionsQuery) String() string { + return "list.partitions" +} + +func (c *ListPartitionsQuery) Stop() { + close(c.stopCh) +} + +func (c *ListPartitionsQuery) Type() Type { + return TypeConsul +} diff --git a/dependency/consul_partitions_test.go b/dependency/consul_partitions_test.go new file mode 100644 index 000000000..6144c6fbe --- /dev/null +++ b/dependency/consul_partitions_test.go @@ -0,0 +1,49 @@ +package dependency + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func init() { + ListPartitionsQuerySleepTime = 50 * time.Millisecond +} + +func TestListPartitionsQuery_Fetch(t *testing.T) { + if !tenancyHelper.IsConsulEnterprise() { + t.Skip("Enterprise only test") + } + + expected := []*Partition{ + { + Name: "default", + Description: "Builtin Default Partition", + }, + { + Name: "foo", + Description: "", + }, + } + + d, err := NewListPartitionsQuery() + require.NoError(t, err) + + act, _, err := d.Fetch(testClients, nil) + require.NoError(t, err) + assert.Equal(t, expected, act) +} + +func TestListPartitionsQuery_FetchError(t *testing.T) { + if tenancyHelper.IsConsulEnterprise() { + t.Skip("CE only test") + } + + d, err := NewListPartitionsQuery() + require.NoError(t, err) + + _, _, err = d.Fetch(testClients, nil) + require.Error(t, err) +} diff --git a/dependency/dependency.go b/dependency/dependency.go index 46d62135f..6c950a26b 100644 --- a/dependency/dependency.go +++ b/dependency/dependency.go @@ -31,6 +31,8 @@ const ( nvListPrefixRe = `/?(?P[^@]*)` nvListNSRe = `(@(?P([[:word:]\-\_]+|\*)))?` nvRegionRe = `(\.(?P[[:word:]\-\_]+))?` + + DefaultNonBlockingQuerySleepTime = 15 * time.Second ) type Type int diff --git a/dependency/dependency_test.go b/dependency/dependency_test.go index 68a69c180..210f7c6eb 100644 --- a/dependency/dependency_test.go +++ b/dependency/dependency_test.go @@ -12,15 +12,15 @@ import ( "os" "os/exec" "reflect" - "runtime" "testing" "time" - "github.com/hashicorp/consul-template/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" nomadapi "github.com/hashicorp/nomad/api" vapi "github.com/hashicorp/vault/api" + + "github.com/hashicorp/consul-template/test" ) const ( @@ -43,6 +43,20 @@ func TestMain(m *testing.M) { tb := &test.TestingTB{} runTestConsul(tb) clients := NewClientSet() + + defer func() { + // Attempt to recover from a panic and stop the server. If we don't + // stop it, the panic will cause the server to remain running in + // the background. Here we catch the panic and the re-raise it. + // This doesn't do anything if we get panics in individual test cases + if r := recover(); r != nil { + testConsul.Stop() + testVault.Stop() + testNomad.Stop() + panic(r) + } + }() + if err := clients.CreateConsulClient(&CreateConsulClientInput{ Address: testConsul.HTTPAddr, }); err != nil { @@ -111,24 +125,7 @@ func TestMain(m *testing.M) { Fatalf("failed to start Nomad: %v\n", err) } - exitCh := make(chan int, 1) - func() { - defer func() { - // Attempt to recover from a panic and stop the server. If we don't - // stop it, the panic will cause the server to remain running in - // the background. Here we catch the panic and the re-raise it. - if r := recover(); r != nil { - testConsul.Stop() - testVault.Stop() - testNomad.Stop() - panic(r) - } - }() - - exitCh <- m.Run() - }() - - exit := <-exitCh + exit := m.Run() tb.DoCleanup() testConsul.Stop() @@ -513,7 +510,7 @@ func TestDeepCopyAndSortTags(t *testing.T) { func Fatalf(format string, args ...interface{}) { fmt.Printf(format, args...) - runtime.Goexit() + os.Exit(1) } func (v *nomadServer) CreateVariable(path string, data map[string]string, opts *nomadapi.WriteOptions) error { @@ -558,6 +555,7 @@ func (c *ClientSet) createConsulPartitions() error { return nil } + func (c *ClientSet) createConsulNs() error { for _, tenancy := range tenancyHelper.TestTenancies() { if tenancy.Namespace != "" && tenancy.Namespace != "default" { diff --git a/dependency/nomad_var_get_test.go b/dependency/nomad_var_get_test.go index cc80473e3..bb2629259 100644 --- a/dependency/nomad_var_get_test.go +++ b/dependency/nomad_var_get_test.go @@ -121,7 +121,6 @@ func TestNewNVGetQuery(t *testing.T) { assert.Equal(t, tc.exp, act) }) } - fmt.Println("done") } func TestNVGetQuery_Fetch(t *testing.T) { diff --git a/dependency/vault_agent_token.go b/dependency/vault_agent_token.go index 320b57691..b5d7ef734 100644 --- a/dependency/vault_agent_token.go +++ b/dependency/vault_agent_token.go @@ -17,7 +17,7 @@ var _ Dependency = (*VaultAgentTokenQuery)(nil) const ( // VaultAgentTokenSleepTime is the amount of time to sleep between queries, since // the fsnotify library is not compatible with solaris and other OSes yet. - VaultAgentTokenSleepTime = 15 * time.Second + VaultAgentTokenSleepTime = DefaultNonBlockingQuerySleepTime ) // VaultAgentTokenQuery is the dependency to Vault Agent token diff --git a/docs/templating-language.md b/docs/templating-language.md index 36e7afc98..4892e5489 100644 --- a/docs/templating-language.md +++ b/docs/templating-language.md @@ -11,6 +11,7 @@ provides the following functions: * [`caRoots`](#caroots) * [`connect`](#connect) * [`datacenters`](#datacenters) + * [`exportedServices`](#exportedservices) * [`file`](#file) * [`key`](#key) * [`keyExists`](#keyexists) @@ -19,6 +20,7 @@ provides the following functions: * [`safeLs`](#safels) * [`node`](#node) * [`nodes`](#nodes) + * [`partitions`](#partitions) * [`peerings`](#peerings) * [`secret`](#secret) + [Format](#format) @@ -245,6 +247,24 @@ environments where performance is a factor. {{ datacenters true }} ``` +### `exportedServices` + +Query [Consul][consul] for all exported services in a given partition. + +```golang +{{ exportedServices "" }} +``` + +For example: + +```golang +{{- range $svc := (exportedServices "default") }} + {{- range $consumer := .Consumers.Partitions }} + {{- $svc.Service }} is exported to the {{ $consumer }} partition + {{- end }} +{{- end }} +``` + ### `file` Read and output the contents of a local file on disk. If the file cannot be @@ -506,6 +526,15 @@ To query a different data center and order by shortest trip time to ourselves: To access map data such as `TaggedAddresses` or `Meta`, use [Go's text/template][text-template] map indexing. +### `partitions` + +Query [Consul][consul] for all partitions. + +```golang +{{ range partitions }} +{{ .Name }} +{{ end }} + ### `peerings` Query [Consul][consul] for all peerings. diff --git a/examples/nginx-connect-proxy/run-nginx-connect-proxy b/examples/nginx-connect-proxy/run-nginx-connect-proxy index f1dca85b0..dd799d3a0 100755 --- a/examples/nginx-connect-proxy/run-nginx-connect-proxy +++ b/examples/nginx-connect-proxy/run-nginx-connect-proxy @@ -1,7 +1,7 @@ #!/bin/sh # This script is here to allow easy testing of the config files embedded in the -# docucument and to help users try it out. +# document and to help users try it out. name=$(basename ${0}) proxy_connect_document="${name#run-}.md" diff --git a/go.mod b/go.mod index 4f9b0c2ca..62540d307 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module github.com/hashicorp/consul-template -go 1.21 +go 1.22 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.26.1 + github.com/hashicorp/consul/api v1.28.3 github.com/hashicorp/consul/sdk v0.15.0 github.com/hashicorp/go-gatedio v0.5.0 - github.com/hashicorp/go-hclog v1.5.0 + github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-rootcerts v1.0.2 github.com/hashicorp/go-sockaddr v1.0.6 @@ -25,14 +25,14 @@ require ( github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sys v0.19.0 + golang.org/x/sys v0.20.0 gopkg.in/yaml.v2 v2.4.0 ) require ( 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-20230817173708-d852ddb80c63 + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/text v0.14.0 ) @@ -41,10 +41,11 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect - github.com/fatih/color v1.14.1 // indirect + github.com/fatih/color v1.17.0 // indirect 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 @@ -54,10 +55,10 @@ require ( github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/miekg/dns v1.1.50 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect diff --git a/go.sum b/go.sum index cd0381cfe..f73590890 100644 --- a/go.sum +++ b/go.sum @@ -36,13 +36,11 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -56,6 +54,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/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= @@ -63,16 +63,19 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= -github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= +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/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/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= @@ -88,8 +91,8 @@ github.com/hashicorp/go-gatedio v0.5.0 h1:Jm1X5yP4yCqqWj5L1TgW7iZwCVPGtVc+mro5r/ github.com/hashicorp/go-gatedio v0.5.0/go.mod h1:Lr3t8L6IyxD3DAeaUxGcgl2JnRUpWMCsmBl4Omu/2t4= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -125,8 +128,8 @@ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= @@ -174,11 +177,12 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= @@ -269,19 +273,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -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/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -296,8 +297,6 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -307,6 +306,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -336,13 +337,11 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -368,12 +367,18 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/template/funcs.go b/template/funcs.go index 09db41976..005847dc1 100644 --- a/template/funcs.go +++ b/template/funcs.go @@ -27,14 +27,15 @@ import ( "github.com/BurntSushi/toml" spewLib "github.com/davecgh/go-spew/spew" - dep "github.com/hashicorp/consul-template/dependency" "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" - yaml "gopkg.in/yaml.v2" + "gopkg.in/yaml.v2" + + dep "github.com/hashicorp/consul-template/dependency" ) // now is function that represents the current time in UTC. This is here @@ -74,6 +75,54 @@ func datacentersFunc(b *Brain, used, missing *dep.Set) func(ignore ...bool) ([]s } } +// partitionsFunc returns or accumulates partition dependencies. +func partitionsFunc(b *Brain, used, missing *dep.Set) func() ([]*dep.Partition, error) { + return func() ([]*dep.Partition, error) { + result := []*dep.Partition{} + + d, err := dep.NewListPartitionsQuery() + if err != nil { + return result, err + } + + used.Add(d) + + if value, ok := b.Recall(d); ok { + return value.([]*dep.Partition), nil + } + + missing.Add(d) + + return result, nil + } +} + +// exportedServicesFunc returns or accumulates partition dependencies. +func exportedServicesFunc(b *Brain, used, missing *dep.Set) func(...string) ([]dep.ExportedService, error) { + return func(s ...string) ([]dep.ExportedService, error) { + result := []dep.ExportedService{} + + if len(s) > 1 { + return result, errors.New("exportedServices: wrong number of arguments, expected 0 or 1") + } + + d, err := dep.NewListExportedServicesQuery(strings.Join(s, "")) + if err != nil { + return result, err + } + + used.Add(d) + + if value, ok := b.Recall(d); ok { + return value.([]dep.ExportedService), nil + } + + missing.Add(d) + + return result, nil + } +} + // envFunc returns a function which checks the value of an environment variable. // Invokers can specify their own environment, which takes precedences over any // real environment variables diff --git a/template/template.go b/template/template.go index 28b5265d1..1aad2524b 100644 --- a/template/template.go +++ b/template/template.go @@ -12,10 +12,11 @@ import ( "text/template" "github.com/Masterminds/sprig/v3" - "github.com/hashicorp/consul-template/config" - dep "github.com/hashicorp/consul-template/dependency" "github.com/pkg/errors" "golang.org/x/exp/maps" + + "github.com/hashicorp/consul-template/config" + dep "github.com/hashicorp/consul-template/dependency" ) var ( @@ -328,26 +329,28 @@ func funcMap(i *funcMapInput) template.FuncMap { r := template.FuncMap{ // API functions - "datacenters": datacentersFunc(i.brain, i.used, i.missing), - "file": fileFunc(i.brain, i.used, i.missing, i.sandboxPath), - "key": keyFunc(i.brain, i.used, i.missing), - "keyExists": keyExistsFunc(i.brain, i.used, i.missing), - "keyOrDefault": keyWithDefaultFunc(i.brain, i.used, i.missing), - "ls": lsFunc(i.brain, i.used, i.missing, true), - "safeLs": safeLsFunc(i.brain, i.used, i.missing), - "node": nodeFunc(i.brain, i.used, i.missing), - "nodes": nodesFunc(i.brain, i.used, i.missing), - "peerings": peeringsFunc(i.brain, i.used, i.missing), - "secret": secretFunc(i.brain, i.used, i.missing), - "secrets": secretsFunc(i.brain, i.used, i.missing), - "service": serviceFunc(i.brain, i.used, i.missing), - "connect": connectFunc(i.brain, i.used, i.missing), - "services": servicesFunc(i.brain, i.used, i.missing), - "tree": treeFunc(i.brain, i.used, i.missing, true), - "safeTree": safeTreeFunc(i.brain, i.used, i.missing), - "caRoots": connectCARootsFunc(i.brain, i.used, i.missing), - "caLeaf": connectLeafFunc(i.brain, i.used, i.missing), - "pkiCert": pkiCertFunc(i.brain, i.used, i.missing, i.destination), + "datacenters": datacentersFunc(i.brain, i.used, i.missing), + "exportedServices": exportedServicesFunc(i.brain, i.used, i.missing), + "file": fileFunc(i.brain, i.used, i.missing, i.sandboxPath), + "key": keyFunc(i.brain, i.used, i.missing), + "keyExists": keyExistsFunc(i.brain, i.used, i.missing), + "keyOrDefault": keyWithDefaultFunc(i.brain, i.used, i.missing), + "ls": lsFunc(i.brain, i.used, i.missing, true), + "safeLs": safeLsFunc(i.brain, i.used, i.missing), + "node": nodeFunc(i.brain, i.used, i.missing), + "nodes": nodesFunc(i.brain, i.used, i.missing), + "partitions": partitionsFunc(i.brain, i.used, i.missing), + "peerings": peeringsFunc(i.brain, i.used, i.missing), + "secret": secretFunc(i.brain, i.used, i.missing), + "secrets": secretsFunc(i.brain, i.used, i.missing), + "service": serviceFunc(i.brain, i.used, i.missing), + "connect": connectFunc(i.brain, i.used, i.missing), + "services": servicesFunc(i.brain, i.used, i.missing), + "tree": treeFunc(i.brain, i.used, i.missing, true), + "safeTree": safeTreeFunc(i.brain, i.used, i.missing), + "caRoots": connectCARootsFunc(i.brain, i.used, i.missing), + "caLeaf": connectLeafFunc(i.brain, i.used, i.missing), + "pkiCert": pkiCertFunc(i.brain, i.used, i.missing, i.destination), // Nomad Functions. "nomadServices": nomadServicesFunc(i.brain, i.used, i.missing),