Skip to content

Commit

Permalink
Add partitions func for listing partitions in the local datacenter
Browse files Browse the repository at this point in the history
  • Loading branch information
nathancoleman committed May 21, 2024
1 parent e9e921d commit 1bd9b0d
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 4 deletions.
112 changes: 112 additions & 0 deletions dependency/consul_partitions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package dependency

import (
"context"
"log"
"net/url"
"slices"
"strings"
"time"

"github.com/hashicorp/consul/api"
"github.com/pkg/errors"
)

var (
// Ensure implements
_ Dependency = (*ListPartitionsQuery)(nil)

// ListPartitionsQuerySleepTime is the amount of time to sleep between
// queries, since the endpoint does not support blocking queries.
ListPartitionsQuerySleepTime = 15 * time.Second
)

// 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):
}
}

// TODO Consider using a proper context
partitions, _, err := clients.Consul().Partitions().List(context.Background(), opts.ToConsulOpts())
if err != nil {
return nil, nil, errors.Wrapf(err, c.String())
}

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.
// TODO What is this?
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
}
10 changes: 10 additions & 0 deletions docs/templating-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ provides the following functions:
* [`safeLs`](#safels)
* [`node`](#node)
* [`nodes`](#nodes)
* [`partitions`](#partitions)
* [`peerings`](#peerings)
* [`secret`](#secret)
+ [Format](#format)
Expand Down Expand Up @@ -506,6 +507,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 }}
{{ . }}{{ end }}
```

### `peerings`

Query [Consul][consul] for all peerings.
Expand Down
27 changes: 25 additions & 2 deletions template/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -74,6 +75,28 @@ 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() ([]string, error) {
return func() ([]string, error) {
result := []string{}

d, err := dep.NewListPartitionsQuery()
if err != nil {
return result, err
}

used.Add(d)

if value, ok := b.Recall(d); ok {
return value.([]string), 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
Expand Down
6 changes: 4 additions & 2 deletions template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -337,6 +338,7 @@ func funcMap(i *funcMapInput) template.FuncMap {
"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),
Expand Down

0 comments on commit 1bd9b0d

Please sign in to comment.