diff --git a/libnetwork/api/api.go b/libnetwork/api/api.go index 4abec5e636..8981f5f6c8 100644 --- a/libnetwork/api/api.go +++ b/libnetwork/api/api.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "net/http" + "strings" "github.com/docker/libnetwork" "github.com/docker/libnetwork/types" @@ -15,6 +16,7 @@ var ( successResponse = responseStatus{Status: "Success", StatusCode: http.StatusOK} createdResponse = responseStatus{Status: "Created", StatusCode: http.StatusCreated} mismatchResponse = responseStatus{Status: "Body/URI parameter mismatch", StatusCode: http.StatusBadRequest} + badQueryresponse = responseStatus{Status: "Unsupported query", StatusCode: http.StatusBadRequest} ) const ( @@ -23,14 +25,19 @@ const ( // Router URL variable definition nwName = "{" + urlNwName + ":" + regex + "}" nwID = "{" + urlNwID + ":" + regex + "}" + nwPID = "{" + urlNwPID + ":" + regex + "}" epName = "{" + urlEpName + ":" + regex + "}" epID = "{" + urlEpID + ":" + regex + "}" + epPID = "{" + urlEpPID + ":" + regex + "}" cnID = "{" + urlCnID + ":" + regex + "}" + // Internal URL variable name, they can be anything urlNwName = "network-name" urlNwID = "network-id" + urlNwPID = "network-partial-id" urlEpName = "endpoint-name" urlEpID = "endpoint-id" + urlEpPID = "endpoint-partial-id" urlCnID = "container-id" ) @@ -77,9 +84,12 @@ func (h *httpHandler) initRouter() { "GET": { // Order matters {"/networks", []string{"name", nwName}, procGetNetworks}, + {"/networks", []string{"partial-id", nwPID}, procGetNetworks}, {"/networks", nil, procGetNetworks}, {"/networks/" + nwID, nil, procGetNetwork}, {"/networks/" + nwID + "/endpoints", []string{"name", epName}, procGetEndpoints}, + {"/networks/" + nwID + "/endpoints", []string{"partial-id", epPID}, procGetEndpoints}, + {"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint}, }, "POST": { @@ -234,12 +244,26 @@ func procGetNetwork(c libnetwork.NetworkController, vars map[string]string, body func procGetNetworks(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { var list []*networkResource - // If query parameter is specified, return a filtered collection - if name, queryByName := vars[urlNwName]; queryByName { - nw, errRsp := findNetwork(c, name, byName) - if errRsp.isOK() { + // Look for query filters and validate + name, queryByName := vars[urlNwName] + shortID, queryByPid := vars[urlNwPID] + if queryByName && queryByPid { + return nil, &badQueryresponse + } + + if queryByName { + if nw, errRsp := findNetwork(c, name, byName); errRsp.isOK() { list = append(list, buildNetworkResource(nw)) } + } else if queryByPid { + // Return all the prefix-matching networks + l := func(nw libnetwork.Network) bool { + if strings.HasPrefix(nw.ID(), shortID) { + list = append(list, buildNetworkResource(nw)) + } + return false + } + c.WalkNetworks(l) } else { for _, nw := range c.Networks() { list = append(list, buildNetworkResource(nw)) @@ -295,6 +319,13 @@ func procGetEndpoint(c libnetwork.NetworkController, vars map[string]string, bod } func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + // Look for query filters and validate + name, queryByName := vars[urlEpName] + shortID, queryByPid := vars[urlEpPID] + if queryByName && queryByPid { + return nil, &badQueryresponse + } + nwT, nwBy := detectNetworkTarget(vars) nw, errRsp := findNetwork(c, nwT, nwBy) if !errRsp.isOK() { @@ -304,11 +335,19 @@ func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, bo var list []*endpointResource // If query parameter is specified, return a filtered collection - if epT, queryByName := vars[urlEpName]; queryByName { - ep, errRsp := findEndpoint(c, nwT, epT, nwBy, byName) - if errRsp.isOK() { + if queryByName { + if ep, errRsp := findEndpoint(c, nwT, name, nwBy, byName); errRsp.isOK() { list = append(list, buildEndpointResource(ep)) } + } else if queryByPid { + // Return all the prefix-matching networks + l := func(ep libnetwork.Endpoint) bool { + if strings.HasPrefix(ep.ID(), shortID) { + list = append(list, buildEndpointResource(ep)) + } + return false + } + nw.WalkEndpoints(l) } else { for _, ep := range nw.Endpoints() { epr := buildEndpointResource(ep) diff --git a/libnetwork/api/api_test.go b/libnetwork/api/api_test.go index dcf76411d1..416cebb3b6 100644 --- a/libnetwork/api/api_test.go +++ b/libnetwork/api/api_test.go @@ -482,6 +482,43 @@ func TestGetNetworksAndEndpoints(t *testing.T) { } } +func TestDetectGetNetworksInvalidQueryComposition(t *testing.T) { + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + + vars := map[string]string{urlNwName: "x", urlNwPID: "y"} + _, errRsp := procGetNetworks(c, vars, nil) + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp) + } +} + +func TestDetectGetEndpointsInvalidQueryComposition(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + err = c.ConfigureNetworkDriver(bridgeNetType, nil) + if err != nil { + t.Fatal(err) + } + + _, err = c.NewNetwork(bridgeNetType, "network", nil) + if err != nil { + t.Fatal(err) + } + + vars := map[string]string{urlNwName: "network", urlEpName: "x", urlEpPID: "y"} + _, errRsp := procGetEndpoints(c, vars, nil) + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp) + } +} + func TestFindNetworkUtil(t *testing.T) { defer netutils.SetupTestNetNS(t)() @@ -1281,6 +1318,29 @@ func TestEndToEnd(t *testing.T) { t.Fatalf("Incongruent resource found: %v", list[0]) } + // Query network by partial id + chars := []byte(nid) + partial := string(chars[0 : len(chars)/2]) + req, err = http.NewRequest("GET", "/networks?partial-id="+partial, nil) + if err != nil { + t.Fatal(err) + } + handleRequest(rsp, req) + if rsp.statusCode != http.StatusOK { + t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body) + } + + err = json.Unmarshal(rsp.body, &list) + if err != nil { + t.Fatal(err) + } + if len(list) == 0 { + t.Fatalf("Expected non empty list") + } + if list[0].Name != "network-fiftyfive" || nid != list[0].ID { + t.Fatalf("Incongruent resource found: %v", list[0]) + } + // Get network by id req, err = http.NewRequest("GET", "/networks/"+nid, nil) if err != nil { @@ -1373,6 +1433,29 @@ func TestEndToEnd(t *testing.T) { t.Fatalf("Incongruent resource found: %v", epList[0]) } + // Query endpoint by partial id + chars = []byte(eid) + partial = string(chars[0 : len(chars)/2]) + req, err = http.NewRequest("GET", "/networks/"+nid+"/endpoints?partial-id="+partial, nil) + if err != nil { + t.Fatal(err) + } + handleRequest(rsp, req) + if rsp.statusCode != http.StatusOK { + t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body) + } + + err = json.Unmarshal(rsp.body, &epList) + if err != nil { + t.Fatal(err) + } + if len(epList) == 0 { + t.Fatalf("Empty response body") + } + if epList[0].Name != "ep-TwentyTwo" || eid != epList[0].ID { + t.Fatalf("Incongruent resource found: %v", epList[0]) + } + // Get endpoint by id req, err = http.NewRequest("GET", "/networks/"+nid+"/endpoints/"+eid, nil) if err != nil {