From c0894f0f50a961ec0a0395ab0bb11d7c9dd4aee3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 28 Mar 2018 10:14:32 -0700 Subject: [PATCH] api: IntentionMatch --- api/connect_intention.go | 68 +++++++++++++++++++++++++++++++++++ api/connect_intention_test.go | 54 +++++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/api/connect_intention.go b/api/connect_intention.go index b138dd4ae..aa2f82d3d 100644 --- a/api/connect_intention.go +++ b/api/connect_intention.go @@ -67,6 +67,22 @@ const ( IntentionSourceConsul IntentionSourceType = "consul" ) +// IntentionMatch are the arguments for the intention match API. +type IntentionMatch struct { + By IntentionMatchType + Names []string +} + +// IntentionMatchType is the target for a match request. For example, +// matching by source will look for all intentions that match the given +// source value. +type IntentionMatchType string + +const ( + IntentionMatchSource IntentionMatchType = "source" + IntentionMatchDestination IntentionMatchType = "destination" +) + // Intentions returns the list of intentions. func (h *Connect) Intentions(q *QueryOptions) ([]*Intention, *QueryMeta, error) { r := h.c.newRequest("GET", "/v1/connect/intentions") @@ -88,6 +104,58 @@ func (h *Connect) Intentions(q *QueryOptions) ([]*Intention, *QueryMeta, error) return out, qm, nil } +// IntentionGet retrieves a single intention. +func (h *Connect) IntentionGet(id string, q *QueryOptions) (*Intention, *QueryMeta, error) { + r := h.c.newRequest("GET", "/v1/connect/intentions/"+id) + r.setQueryOptions(q) + rtt, resp, err := requireOK(h.c.doRequest(r)) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + qm := &QueryMeta{} + parseQueryMeta(resp, qm) + qm.RequestTime = rtt + + var out Intention + if err := decodeBody(resp, &out); err != nil { + return nil, nil, err + } + return &out, qm, nil +} + +// IntentionMatch returns the list of intentions that match a given source +// or destination. The returned intentions are ordered by precedence where +// result[0] is the highest precedence (if that matches, then that rule overrides +// all other rules). +// +// Matching can be done for multiple names at the same time. The resulting +// map is keyed by the given names. Casing is preserved. +func (h *Connect) IntentionMatch(args *IntentionMatch, q *QueryOptions) (map[string][]*Intention, *QueryMeta, error) { + r := h.c.newRequest("GET", "/v1/connect/intentions/match") + r.setQueryOptions(q) + r.params.Set("by", string(args.By)) + for _, name := range args.Names { + r.params.Add("name", name) + } + rtt, resp, err := requireOK(h.c.doRequest(r)) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + qm := &QueryMeta{} + parseQueryMeta(resp, qm) + qm.RequestTime = rtt + + var out map[string][]*Intention + if err := decodeBody(resp, &out); err != nil { + return nil, nil, err + } + return out, qm, nil +} + // IntentionCreate will create a new intention. The ID in the given // structure must be empty and a generate ID will be returned on // success. diff --git a/api/connect_intention_test.go b/api/connect_intention_test.go index 2fc742602..0edcf4c49 100644 --- a/api/connect_intention_test.go +++ b/api/connect_intention_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestAPI_ConnectIntentionCreate(t *testing.T) { +func TestAPI_ConnectIntentionCreateListGet(t *testing.T) { t.Parallel() require := require.New(t) @@ -33,6 +33,58 @@ func TestAPI_ConnectIntentionCreate(t *testing.T) { ixn.CreateIndex = actual.CreateIndex ixn.ModifyIndex = actual.ModifyIndex require.Equal(ixn, actual) + + // Get it + actual, _, err = connect.IntentionGet(id, nil) + require.Nil(err) + require.Equal(ixn, actual) +} + +func TestAPI_ConnectIntentionMatch(t *testing.T) { + t.Parallel() + + require := require.New(t) + c, s := makeClient(t) + defer s.Stop() + + connect := c.Connect() + + // Create + { + insert := [][]string{ + {"foo", "*"}, + {"foo", "bar"}, + {"foo", "baz"}, // shouldn't match + {"bar", "bar"}, // shouldn't match + {"bar", "*"}, // shouldn't match + {"*", "*"}, + } + + for _, v := range insert { + ixn := testIntention() + ixn.DestinationNS = v[0] + ixn.DestinationName = v[1] + id, _, err := connect.IntentionCreate(ixn, nil) + require.Nil(err) + require.NotEmpty(id) + } + } + + // Match it + result, _, err := connect.IntentionMatch(&IntentionMatch{ + By: IntentionMatchDestination, + Names: []string{"foo/bar"}, + }, nil) + require.Nil(err) + require.Len(result, 1) + + var actual [][]string + expected := [][]string{{"foo", "bar"}, {"foo", "*"}, {"*", "*"}} + for _, ixn := range result["foo/bar"] { + actual = append(actual, []string{ixn.DestinationNS, ixn.DestinationName}) + } + + require.Equal(expected, actual) } func testIntention() *Intention {