diff --git a/agent/http_oss.go b/agent/http_oss.go index 4a2017d28..28ece14ae 100644 --- a/agent/http_oss.go +++ b/agent/http_oss.go @@ -39,6 +39,7 @@ func init() { registerEndpoint("/v1/catalog/services", []string{"GET"}, (*HTTPServer).CatalogServices) registerEndpoint("/v1/catalog/service/", []string{"GET"}, (*HTTPServer).CatalogServiceNodes) registerEndpoint("/v1/catalog/node/", []string{"GET"}, (*HTTPServer).CatalogNodeServices) + registerEndpoint("/v1/connect/intentions", []string{"GET"}, (*HTTPServer).IntentionList) registerEndpoint("/v1/coordinate/datacenters", []string{"GET"}, (*HTTPServer).CoordinateDatacenters) registerEndpoint("/v1/coordinate/nodes", []string{"GET"}, (*HTTPServer).CoordinateNodes) registerEndpoint("/v1/coordinate/node/", []string{"GET"}, (*HTTPServer).CoordinateNode) diff --git a/agent/intentions_endpoint.go b/agent/intentions_endpoint.go new file mode 100644 index 000000000..0cdd0dc43 --- /dev/null +++ b/agent/intentions_endpoint.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "github.com/hashicorp/consul/agent/structs" +) + +// /v1/connect/intentions +func (s *HTTPServer) IntentionList(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + if req.Method != "GET" { + return nil, MethodNotAllowedError{req.Method, []string{"GET"}} + } + + var args structs.DCSpecificRequest + if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { + return nil, nil + } + + var reply structs.IndexedIntentions + if err := s.agent.RPC("Intention.List", &args, &reply); err != nil { + return nil, err + } + + // Use empty list instead of nil. + if reply.Intentions == nil { + reply.Intentions = make(structs.Intentions, 0) + } + return reply.Intentions, nil +} diff --git a/agent/intentions_endpoint_test.go b/agent/intentions_endpoint_test.go new file mode 100644 index 000000000..2e56eabf7 --- /dev/null +++ b/agent/intentions_endpoint_test.go @@ -0,0 +1,71 @@ +package agent + +import ( + "net/http" + "net/http/httptest" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/consul/agent/structs" +) + +func TestIntentionsList_empty(t *testing.T) { + t.Parallel() + + a := NewTestAgent(t.Name(), "") + defer a.Shutdown() + + // Make sure an empty list is non-nil. + req, _ := http.NewRequest("GET", "/v1/connect/intentions", nil) + resp := httptest.NewRecorder() + obj, err := a.srv.IntentionList(resp, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + value := obj.(structs.Intentions) + if value == nil || len(value) != 0 { + t.Fatalf("bad: %v", value) + } +} + +func TestIntentionsList_values(t *testing.T) { + t.Parallel() + + a := NewTestAgent(t.Name(), "") + defer a.Shutdown() + + // Create some intentions + for _, v := range []string{"foo", "bar"} { + req := structs.IntentionRequest{ + Datacenter: "dc1", + Op: structs.IntentionOpCreate, + Intention: &structs.Intention{SourceName: v}, + } + var reply string + if err := a.RPC("Intention.Apply", &req, &reply); err != nil { + t.Fatalf("err: %s", err) + } + } + + // Request + req, _ := http.NewRequest("GET", "/v1/connect/intentions", nil) + resp := httptest.NewRecorder() + obj, err := a.srv.IntentionList(resp, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + value := obj.(structs.Intentions) + if len(value) != 2 { + t.Fatalf("bad: %v", value) + } + + expected := []string{"bar", "foo"} + actual := []string{value[0].SourceName, value[1].SourceName} + sort.Strings(actual) + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +}