[lxc-devel] [lxd/master] Basic Filtering on RestAPI Get Requests

jdeans289 on Github lxc-bot at linuxcontainers.org
Thu Dec 12 06:57:46 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 675 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191211/db663188/attachment-0001.bin>
-------------- next part --------------
From 0606a3ba0bb41ba5362657c34ef9e94ae4076ad4 Mon Sep 17 00:00:00 2001
From: Jacqueline Deans <jdeans289 at gmail.com>
Date: Fri, 6 Dec 2019 20:58:16 -0600
Subject: [PATCH 01/13] lxd: added code to parse filter query

---
 lxd/containers_get.go | 79 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 78 insertions(+), 1 deletion(-)

diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 5fe0015cf3..e7ac30c513 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -43,10 +43,66 @@ func urlInstanceTypeDetect(r *http.Request) (instancetype.Type, error) {
 	return instancetype.Any, nil
 }
 
+func doFilter (fstr string, result interface{}) interface{} {
+	// if matches filter, return true
+	logger.Warnf("JackieError: %s", result)
+	applyFilter(fstr, result)
+	return result
+}
+
+func applyFilter (fstr string, container interface{}) bool {
+	filterSplit := strings.Fields(fstr)
+
+	index := 0
+	result := true
+	prevLogical := "and"
+
+	queryLen := len(filterSplit)
+
+	for index < queryLen {
+		field := filterSplit[index]
+		operator := filterSplit[index+1]
+		value := filterSplit[index+2]
+		index+=3
+
+		// eval 
+		logger.Warnf("JackieError: evaluating %s %s %s", field, operator, value)
+		curResult := evaluateField(field, value, operator, "first");
+		if operator == "neq" {
+			curResult = !curResult
+		}
+
+		logger.Warnf("JackieError: %s", prevLogical)
+		if prevLogical == "and" {
+			logger.Warnf("JackieError: Logical AND")
+			result = curResult && result
+		} else {
+			logger.Warnf("JackieError: Logical OR")
+			result = curResult || result
+		}
+
+		if index < queryLen {
+			prevLogical = filterSplit[index]
+			index++
+		}
+	}
+
+	return result
+}
+
+func evaluateField (field string, value string, op string, container string) bool {
+	return true
+}
+
+
 func containersGet(d *Daemon, r *http.Request) response.Response {
 	for i := 0; i < 100; i++ {
 		result, err := doContainersGet(d, r)
 		if err == nil {
+			filterStr := r.FormValue("filter")
+			// if filterStr != "" {
+			result = doFilter(filterStr, result)
+			// }
 			return response.SyncResponse(true, result)
 		}
 		if !query.IsRetriableError(err) {
@@ -77,6 +133,14 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	// Parse the recursion field
 	recursionStr := r.FormValue("recursion")
 
+	// // Parse filter value
+	filterStr := r.FormValue("filter")
+
+	if filterStr != "" {
+		logger.Warnf("JackieError: %s", filterStr)
+
+	}
+
 	recursion, err := strconv.Atoi(recursionStr)
 	if err != nil {
 		recursion = 0
@@ -106,7 +170,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	if err != nil {
 		return []string{}, err
 	}
-
+	logger.Warnf("JackieError: RESULT %s", result)
 	// Get the local instances
 	nodeCts := map[string]instance.Instance{}
 	if recursion > 0 {
@@ -130,6 +194,8 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 				Location:   nodes[name],
 			}
 		}
+		// doFilter("filterStr", c)
+		logger.Warnf("JackieError: CHECK 1")
 		resultMu.Lock()
 		resultList = append(resultList, &c)
 		resultMu.Unlock()
@@ -144,6 +210,8 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 				Location:   nodes[name],
 			}}
 		}
+		// doFilter("filterStr", c)
+		logger.Warnf("JackieError: CHECK 2")
 		resultMu.Lock()
 		resultFullList = append(resultFullList, &c)
 		resultMu.Unlock()
@@ -223,6 +291,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 					instancePath = "virtual-machines"
 				}
 				url := fmt.Sprintf("/%s/%s/%s", version.APIVersion, instancePath, container)
+				logger.Warnf("JackieError: CHECK 3")
 				resultString = append(resultString, url)
 			}
 		} else {
@@ -275,7 +344,13 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	}
 	wg.Wait()
 
+	// if recursion == 2 {
+	// 	return []string{"Hello!!!!!!!"}, nil
+	// }
+
 	if recursion == 0 {
+		logger.Warnf("JackieError: CHECK 4")
+		logger.Warnf("JackieError: Result String %s", resultList)
 		return resultString, nil
 	}
 
@@ -285,6 +360,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 			return resultList[i].Name < resultList[j].Name
 		})
 
+		logger.Warnf("JackieError: Result List %s", resultList)
 		return resultList, nil
 	}
 
@@ -293,6 +369,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 		return resultFullList[i].Name < resultFullList[j].Name
 	})
 
+	logger.Warnf("JackieError: Result Full List %s", resultList)
 	return resultFullList, nil
 }
 

From d089d522f7d5fe10f536dc6239a40f3cb2d132d0 Mon Sep 17 00:00:00 2001
From: Jacqueline Deans <jdeans289 at gmail.com>
Date: Fri, 6 Dec 2019 23:01:09 -0600
Subject: [PATCH 02/13] Progress on filtering functionality

---
 lxd/containers_get.go | 84 +++++++++++++++++++++++++++++++++++--------
 1 file changed, 69 insertions(+), 15 deletions(-)

diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index e7ac30c513..4f1a405e71 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -43,14 +43,41 @@ func urlInstanceTypeDetect(r *http.Request) (instancetype.Type, error) {
 	return instancetype.Any, nil
 }
 
-func doFilter (fstr string, result interface{}) interface{} {
+// func doFilter(fstr string, container *api.InstanceFull) bool {
+// 	return applyFilter(fstr, &(container.Instance))
+// }
+
+func doFilter (fstr string, result map[string][]string, d *Daemon) map[string][]string {
 	// if matches filter, return true
 	logger.Warnf("JackieError: %s", result)
-	applyFilter(fstr, result)
-	return result
+	newResult := map[string][]string{}
+	for address, containers := range result {
+		// if address == "" {
+		// 	address = "default"
+		// }
+
+		newContainers := []string{}
+		logger.Warnf("JackieError: %s", address)
+		for _,container := range containers {
+			logger.Warnf("\tJackieError: %s", container)
+			inst, err := instanceLoadByProjectAndName(d.State(), address, container)
+
+			if err != nil {
+				continue
+			}
+
+			if applyFilter(fstr, inst) {
+				newContainers = append(newContainers, container)
+			}
+		}
+
+		newResult[address] = newContainers
+	}
+
+	return newResult
 }
 
-func applyFilter (fstr string, container interface{}) bool {
+func applyFilter (fstr string, container instance.Instance) bool {
 	filterSplit := strings.Fields(fstr)
 
 	index := 0
@@ -67,10 +94,8 @@ func applyFilter (fstr string, container interface{}) bool {
 
 		// eval 
 		logger.Warnf("JackieError: evaluating %s %s %s", field, operator, value)
-		curResult := evaluateField(field, value, operator, "first");
-		if operator == "neq" {
-			curResult = !curResult
-		}
+
+		curResult := evaluateField(field, value, operator, container);
 
 		logger.Warnf("JackieError: %s", prevLogical)
 		if prevLogical == "and" {
@@ -90,8 +115,22 @@ func applyFilter (fstr string, container interface{}) bool {
 	return result
 }
 
-func evaluateField (field string, value string, op string, container string) bool {
-	return true
+func evaluateField (field string, value string, op string, container instance.Instance) bool {
+	result := false
+	switch(field) {
+		case "name":
+			result = value == container.Name()
+			break
+
+		default:
+			result = false
+	}
+
+	if op == "neq" {
+		result = !result
+	}
+
+	return result
 }
 
 
@@ -99,9 +138,9 @@ func containersGet(d *Daemon, r *http.Request) response.Response {
 	for i := 0; i < 100; i++ {
 		result, err := doContainersGet(d, r)
 		if err == nil {
-			filterStr := r.FormValue("filter")
+			// filterStr := r.FormValue("filter")
 			// if filterStr != "" {
-			result = doFilter(filterStr, result)
+			// result = doFilter(filterStr, result)
 			// }
 			return response.SyncResponse(true, result)
 		}
@@ -138,7 +177,6 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 
 	if filterStr != "" {
 		logger.Warnf("JackieError: %s", filterStr)
-
 	}
 
 	recursion, err := strconv.Atoi(recursionStr)
@@ -148,6 +186,14 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 
 	// Parse the project field
 	project := projectParam(r)
+	// logger.Warnf("JackieError: Project param: %s", project)
+
+
+	inst, err := instanceLoadByProjectAndName(d.State(), project, "first")
+	// doFilter(filterStr, inst)
+	// c,_,_ := inst.Render()
+	// c.ExtendedConfig
+	logger.Warnf("GOT INSTANCE: %d", inst.ID)
 
 	// Get the list and location of all containers
 	var result map[string][]string // Containers by node address
@@ -195,7 +241,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 			}
 		}
 		// doFilter("filterStr", c)
-		logger.Warnf("JackieError: CHECK 1")
+		logger.Warnf("JackieError: CHECK 1 - c = %s", c)
 		resultMu.Lock()
 		resultList = append(resultList, &c)
 		resultMu.Unlock()
@@ -217,6 +263,14 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 		resultMu.Unlock()
 	}
 
+	// TIME TO FILTER
+	logger.Warnf("JackieError: RESULT2 %s", result)
+	if filterStr != "" {
+		result = doFilter(filterStr, result, d)
+	}	
+	
+	logger.Warnf("JackieError: result after filter %s", result)
+
 	// Get the data
 	wg := sync.WaitGroup{}
 	for address, containers := range result {
@@ -360,7 +414,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 			return resultList[i].Name < resultList[j].Name
 		})
 
-		logger.Warnf("JackieError: Result List %s", resultList)
+		// logger.Warnf("JackieError: Result List %s", resultList)
 		return resultList, nil
 	}
 

From 9067ff641fb0df814110e3a86eba0db6275709f4 Mon Sep 17 00:00:00 2001
From: Jacqueline Deans <jdeans289 at gmail.com>
Date: Fri, 6 Dec 2019 23:04:26 -0600
Subject: [PATCH 03/13] Made progress on filtering

---
 lxd/containers_get.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 4f1a405e71..84d592c821 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -121,7 +121,7 @@ func evaluateField (field string, value string, op string, container instance.In
 		case "name":
 			result = value == container.Name()
 			break
-
+		
 		default:
 			result = false
 	}
@@ -268,7 +268,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	if filterStr != "" {
 		result = doFilter(filterStr, result, d)
 	}	
-	
+
 	logger.Warnf("JackieError: result after filter %s", result)
 
 	// Get the data

From acdbd83a1259dbcd7eec6c0102e86960421964a9 Mon Sep 17 00:00:00 2001
From: Jacqueline Deans <jdeans289 at gmail.com>
Date: Sat, 7 Dec 2019 00:00:53 -0600
Subject: [PATCH 04/13] Added config case to filter

---
 lxd/containers_get.go | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 84d592c821..9246c6dd4f 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -61,6 +61,7 @@ func doFilter (fstr string, result map[string][]string, d *Daemon) map[string][]
 		for _,container := range containers {
 			logger.Warnf("\tJackieError: %s", container)
 			inst, err := instanceLoadByProjectAndName(d.State(), address, container)
+			// logger.Warnf("\tJackieError: %s", inst.Config())
 
 			if err != nil {
 				continue
@@ -117,11 +118,19 @@ func applyFilter (fstr string, container instance.Instance) bool {
 
 func evaluateField (field string, value string, op string, container instance.Instance) bool {
 	result := false
-	switch(field) {
-		case "name":
+	// logger.Warnf("JackieError %q", container.ExpandedConfig())
+	switch {
+		case field == "name":
 			result = value == container.Name()
 			break
-		
+
+		case strings.HasPrefix(field, "config"):
+			fieldCut := field[7:len(field)]
+			logger.Warnf("Field chopped: %s", fieldCut)
+			config := container.ExpandedConfig()
+			result = config[fieldCut] == value
+			break
+
 		default:
 			result = false
 	}

From fb58baef9e7b2cba493836162e64b0073a24b963 Mon Sep 17 00:00:00 2001
From: Jacqueline Deans <jdeans289 at gmail.com>
Date: Mon, 9 Dec 2019 00:08:37 -0600
Subject: [PATCH 05/13] Implemented more fields for filtering

---
 lxd/containers_get.go | 99 ++++++++++++++++++++-----------------------
 1 file changed, 45 insertions(+), 54 deletions(-)

diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 9246c6dd4f..15ea7c48cb 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -22,6 +22,7 @@ import (
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
+	"github.com/lxc/lxd/shared/osarch"
 )
 
 // urlInstanceTypeDetect detects what sort of instance type filter is being requested. Either
@@ -43,25 +44,13 @@ func urlInstanceTypeDetect(r *http.Request) (instancetype.Type, error) {
 	return instancetype.Any, nil
 }
 
-// func doFilter(fstr string, container *api.InstanceFull) bool {
-// 	return applyFilter(fstr, &(container.Instance))
-// }
 
 func doFilter (fstr string, result map[string][]string, d *Daemon) map[string][]string {
-	// if matches filter, return true
-	logger.Warnf("JackieError: %s", result)
 	newResult := map[string][]string{}
 	for address, containers := range result {
-		// if address == "" {
-		// 	address = "default"
-		// }
-
 		newContainers := []string{}
-		logger.Warnf("JackieError: %s", address)
 		for _,container := range containers {
-			logger.Warnf("\tJackieError: %s", container)
 			inst, err := instanceLoadByProjectAndName(d.State(), address, container)
-			// logger.Warnf("\tJackieError: %s", inst.Config())
 
 			if err != nil {
 				continue
@@ -84,26 +73,31 @@ func applyFilter (fstr string, container instance.Instance) bool {
 	index := 0
 	result := true
 	prevLogical := "and"
+	not := false
 
 	queryLen := len(filterSplit)
 
 	for index < queryLen {
+		if (filterSplit[index] == "not") {
+			not = true
+			index++
+		}
 		field := filterSplit[index]
 		operator := filterSplit[index+1]
 		value := filterSplit[index+2]
 		index+=3
 
 		// eval 
-		logger.Warnf("JackieError: evaluating %s %s %s", field, operator, value)
-
 		curResult := evaluateField(field, value, operator, container);
 
-		logger.Warnf("JackieError: %s", prevLogical)
+		if not {
+			not = false
+			curResult = !curResult
+		}
+
 		if prevLogical == "and" {
-			logger.Warnf("JackieError: Logical AND")
 			result = curResult && result
 		} else {
-			logger.Warnf("JackieError: Logical OR")
 			result = curResult || result
 		}
 
@@ -118,24 +112,51 @@ func applyFilter (fstr string, container instance.Instance) bool {
 
 func evaluateField (field string, value string, op string, container instance.Instance) bool {
 	result := false
-	// logger.Warnf("JackieError %q", container.ExpandedConfig())
+
 	switch {
-		case field == "name":
+		case strings.EqualFold(field, "name"):
 			result = value == container.Name()
 			break
 
+		case strings.EqualFold(field, "architecture"):
+			archName, _:= osarch.ArchitectureName(container.Architecture())
+			result = value == archName
+			break
+
+		case strings.EqualFold(field, "location"):
+			result = value == container.Location()
+			break
+
+		case strings.EqualFold(field,"status") || strings.EqualFold(field, "state"):
+			result = container.State() == value
+			break
+
+		case strings.EqualFold(field, "project"):
+			result = container.Project() == value
+			break
+
 		case strings.HasPrefix(field, "config"):
 			fieldCut := field[7:len(field)]
-			logger.Warnf("Field chopped: %s", fieldCut)
 			config := container.ExpandedConfig()
 			result = config[fieldCut] == value
 			break
 
+		case strings.HasPrefix(field, "device"):
+			fieldSplit := strings.Split(field,".")
+			valSplit := strings.Split(value,".")
+			devices := container.ExpandedDevices()
+			dev := devices[valSplit[0]]
+			result = dev != nil
+			if len(fieldSplit) > 1 && result {
+				result = dev[fieldSplit[1]] == valSplit[1]
+			}
+			break
+
 		default:
-			result = false
+			return false
 	}
 
-	if op == "neq" {
+	if op == "ne" {
 		result = !result
 	}
 
@@ -147,10 +168,6 @@ func containersGet(d *Daemon, r *http.Request) response.Response {
 	for i := 0; i < 100; i++ {
 		result, err := doContainersGet(d, r)
 		if err == nil {
-			// filterStr := r.FormValue("filter")
-			// if filterStr != "" {
-			// result = doFilter(filterStr, result)
-			// }
 			return response.SyncResponse(true, result)
 		}
 		if !query.IsRetriableError(err) {
@@ -184,10 +201,6 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	// // Parse filter value
 	filterStr := r.FormValue("filter")
 
-	if filterStr != "" {
-		logger.Warnf("JackieError: %s", filterStr)
-	}
-
 	recursion, err := strconv.Atoi(recursionStr)
 	if err != nil {
 		recursion = 0
@@ -195,14 +208,6 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 
 	// Parse the project field
 	project := projectParam(r)
-	// logger.Warnf("JackieError: Project param: %s", project)
-
-
-	inst, err := instanceLoadByProjectAndName(d.State(), project, "first")
-	// doFilter(filterStr, inst)
-	// c,_,_ := inst.Render()
-	// c.ExtendedConfig
-	logger.Warnf("GOT INSTANCE: %d", inst.ID)
 
 	// Get the list and location of all containers
 	var result map[string][]string // Containers by node address
@@ -225,7 +230,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	if err != nil {
 		return []string{}, err
 	}
-	logger.Warnf("JackieError: RESULT %s", result)
+
 	// Get the local instances
 	nodeCts := map[string]instance.Instance{}
 	if recursion > 0 {
@@ -249,8 +254,6 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 				Location:   nodes[name],
 			}
 		}
-		// doFilter("filterStr", c)
-		logger.Warnf("JackieError: CHECK 1 - c = %s", c)
 		resultMu.Lock()
 		resultList = append(resultList, &c)
 		resultMu.Unlock()
@@ -265,20 +268,16 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 				Location:   nodes[name],
 			}}
 		}
-		// doFilter("filterStr", c)
-		logger.Warnf("JackieError: CHECK 2")
 		resultMu.Lock()
 		resultFullList = append(resultFullList, &c)
 		resultMu.Unlock()
 	}
 
-	// TIME TO FILTER
-	logger.Warnf("JackieError: RESULT2 %s", result)
+	// Filter the results
 	if filterStr != "" {
 		result = doFilter(filterStr, result, d)
 	}	
 
-	logger.Warnf("JackieError: result after filter %s", result)
 
 	// Get the data
 	wg := sync.WaitGroup{}
@@ -354,7 +353,6 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 					instancePath = "virtual-machines"
 				}
 				url := fmt.Sprintf("/%s/%s/%s", version.APIVersion, instancePath, container)
-				logger.Warnf("JackieError: CHECK 3")
 				resultString = append(resultString, url)
 			}
 		} else {
@@ -407,13 +405,8 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	}
 	wg.Wait()
 
-	// if recursion == 2 {
-	// 	return []string{"Hello!!!!!!!"}, nil
-	// }
 
 	if recursion == 0 {
-		logger.Warnf("JackieError: CHECK 4")
-		logger.Warnf("JackieError: Result String %s", resultList)
 		return resultString, nil
 	}
 
@@ -423,7 +416,6 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 			return resultList[i].Name < resultList[j].Name
 		})
 
-		// logger.Warnf("JackieError: Result List %s", resultList)
 		return resultList, nil
 	}
 
@@ -432,7 +424,6 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 		return resultFullList[i].Name < resultFullList[j].Name
 	})
 
-	logger.Warnf("JackieError: Result Full List %s", resultList)
 	return resultFullList, nil
 }
 

From d5638074de78eb35595df203ed6efae82f885717 Mon Sep 17 00:00:00 2001
From: Jacqueline Deans <jdeans289 at gmail.com>
Date: Mon, 9 Dec 2019 01:57:13 -0600
Subject: [PATCH 06/13] Added better handling for types

---
 lxd/api.go            |  73 ++++++++++++
 lxd/containers_get.go | 250 ++++++++++++++++++++++++++++--------------
 lxd/images.go         |  30 ++++-
 3 files changed, 265 insertions(+), 88 deletions(-)

diff --git a/lxd/api.go b/lxd/api.go
index cdca5ae0c7..d4abf0dfc8 100644
--- a/lxd/api.go
+++ b/lxd/api.go
@@ -4,6 +4,7 @@ import (
 	"net/http"
 	"net/url"
 	"strings"
+	"reflect"
 
 	log "github.com/lxc/lxd/shared/log15"
 
@@ -12,6 +13,7 @@ import (
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared/logger"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // RestServer creates an http.Server capable of handling requests against the LXD REST
@@ -149,3 +151,74 @@ func queryParam(request *http.Request, key string) string {
 
 	return values.Get(key)
 }
+
+func doFilterNew (fstr string, result []interface{}) []interface{} {
+	newResult := result[:0]
+	for _,obj := range result {
+		if applyFilterNew(fstr, obj) {
+			newResult = append(newResult, obj)
+		}
+	}
+	return newResult
+}
+
+func applyFilterNew (fstr string, obj interface{}) bool {
+	filterSplit := strings.Fields(fstr)
+
+	index := 0
+	result := true
+	prevLogical := "and"
+	not := false
+
+	queryLen := len(filterSplit)
+
+	for index < queryLen {
+		if (filterSplit[index] == "not") {
+			not = true
+			index++
+		}
+		field := filterSplit[index]
+		operator := filterSplit[index+1]
+		value := filterSplit[index+2]
+		index+=3
+		// eval 
+
+		curResult := false
+		
+		logger.Warnf("JackieError: %s", reflect.TypeOf(obj))
+		objType := reflect.TypeOf(obj).String()
+		switch (objType) {
+			case "*api.Instance":
+				curResult = evaluateFieldInstance(field, value, operator, obj.(*api.Instance))
+				break
+			case "*api.InstanceFull":
+				curResult = evaluateFieldInstanceFull(field, value, operator, obj.(*api.InstanceFull))
+				break
+			case "*api.Image":
+				curResult = evaluateFieldImage(field, value, operator, obj.(*api.Image))
+				break
+			default:
+				logger.Warnf("Unable to identify type")
+				break
+
+		}
+
+		if not {
+			not = false
+			curResult = !curResult
+		}
+
+		if prevLogical == "and" {
+			result = curResult && result
+		} else {
+			result = curResult || result
+		}
+
+		if index < queryLen {
+			prevLogical = filterSplit[index]
+			index++
+		}
+	}
+
+	return result
+}
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 15ea7c48cb..5f3641200d 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -22,7 +22,7 @@ import (
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
-	"github.com/lxc/lxd/shared/osarch"
+	// "github.com/lxc/lxd/shared/osarch"
 )
 
 // urlInstanceTypeDetect detects what sort of instance type filter is being requested. Either
@@ -44,107 +44,116 @@ func urlInstanceTypeDetect(r *http.Request) (instancetype.Type, error) {
 	return instancetype.Any, nil
 }
 
-
-func doFilter (fstr string, result map[string][]string, d *Daemon) map[string][]string {
-	newResult := map[string][]string{}
-	for address, containers := range result {
-		newContainers := []string{}
-		for _,container := range containers {
-			inst, err := instanceLoadByProjectAndName(d.State(), address, container)
-
-			if err != nil {
-				continue
-			}
-
-			if applyFilter(fstr, inst) {
-				newContainers = append(newContainers, container)
-			}
-		}
-
-		newResult[address] = newContainers
-	}
-
-	return newResult
-}
-
-func applyFilter (fstr string, container instance.Instance) bool {
-	filterSplit := strings.Fields(fstr)
-
-	index := 0
-	result := true
-	prevLogical := "and"
-	not := false
-
-	queryLen := len(filterSplit)
-
-	for index < queryLen {
-		if (filterSplit[index] == "not") {
-			not = true
-			index++
-		}
-		field := filterSplit[index]
-		operator := filterSplit[index+1]
-		value := filterSplit[index+2]
-		index+=3
-
-		// eval 
-		curResult := evaluateField(field, value, operator, container);
-
-		if not {
-			not = false
-			curResult = !curResult
-		}
-
-		if prevLogical == "and" {
-			result = curResult && result
-		} else {
-			result = curResult || result
-		}
-
-		if index < queryLen {
-			prevLogical = filterSplit[index]
-			index++
-		}
-	}
-
-	return result
+// func doFilterNew (fstr string, result []interface{}) []interface{} {
+// 	newResult := result[:0]
+// 	for _,obj := range result {
+// 		if applyFilterNew(fstr, obj) {
+// 			newResult = append(newResult, obj)
+// 		}
+// 	}
+// 	return newResult
+// }
+
+// func doFilter (fstr string, result map[string][]string, d *Daemon) map[string][]string {
+// 	newResult := map[string][]string{}
+// 	for address, containers := range result {
+// 		newContainers := []string{}
+// 		for _,container := range containers {
+// 			inst, err := instanceLoadByProjectAndName(d.State(), address, container)
+
+// 			if err != nil {
+// 				continue
+// 			}
+
+// 			if applyFilter(fstr, inst) {
+// 				newContainers = append(newContainers, container)
+// 			}
+// 		}
+
+// 		newResult[address] = newContainers
+// 	}
+
+// 	return newResult
+// }
+
+// func applyFilter (fstr string, container instance.Instance) bool {
+// 	filterSplit := strings.Fields(fstr)
+
+// 	index := 0
+// 	result := true
+// 	prevLogical := "and"
+// 	not := false
+
+// 	queryLen := len(filterSplit)
+
+// 	for index < queryLen {
+// 		if (filterSplit[index] == "not") {
+// 			not = true
+// 			index++
+// 		}
+// 		field := filterSplit[index]
+// 		operator := filterSplit[index+1]
+// 		value := filterSplit[index+2]
+// 		index+=3
+
+// 		// eval 
+// 		curResult := evaluateField(field, value, operator, container);
+
+// 		if not {
+// 			not = false
+// 			curResult = !curResult
+// 		}
+
+// 		if prevLogical == "and" {
+// 			result = curResult && result
+// 		} else {
+// 			result = curResult || result
+// 		}
+
+// 		if index < queryLen {
+// 			prevLogical = filterSplit[index]
+// 			index++
+// 		}
+// 	}
+
+// 	return result
+// }
+
+func evaluateFieldInstanceFull(field string, value string, op string, instFull *api.InstanceFull) bool {
+	return evaluateFieldInstance(field, value, op, &instFull.Instance)
 }
 
-func evaluateField (field string, value string, op string, container instance.Instance) bool {
+func evaluateFieldInstance(field string, value string, op string, container *api.Instance) bool {
 	result := false
 
 	switch {
 		case strings.EqualFold(field, "name"):
-			result = value == container.Name()
-			break
-
-		case strings.EqualFold(field, "architecture"):
-			archName, _:= osarch.ArchitectureName(container.Architecture())
-			result = value == archName
+			logger.Warnf("In name eval, %s == %s", value, container.Name)
+			result = value == container.Name
 			break
 
 		case strings.EqualFold(field, "location"):
-			result = value == container.Location()
+			result = value == container.Location
 			break
 
 		case strings.EqualFold(field,"status") || strings.EqualFold(field, "state"):
-			result = container.State() == value
+			result = container.Status == value
 			break
 
-		case strings.EqualFold(field, "project"):
-			result = container.Project() == value
+		case strings.EqualFold(field,"type"):
+			result = container.Type == value
 			break
 
 		case strings.HasPrefix(field, "config"):
 			fieldCut := field[7:len(field)]
-			config := container.ExpandedConfig()
+			config := container.ExpandedConfig
 			result = config[fieldCut] == value
 			break
 
 		case strings.HasPrefix(field, "device"):
 			fieldSplit := strings.Split(field,".")
 			valSplit := strings.Split(value,".")
-			devices := container.ExpandedDevices()
+			devices := container.ExpandedDevices
 			dev := devices[valSplit[0]]
 			result = dev != nil
 			if len(fieldSplit) > 1 && result {
@@ -163,10 +172,69 @@ func evaluateField (field string, value string, op string, container instance.In
 	return result
 }
 
+// func evaluateField (field string, value string, op string, container instance.Instance) bool {
+// 	result := false
+
+// 	switch {
+// 		case strings.EqualFold(field, "name"):
+// 			result = value == container.Name()
+// 			break
+
+// 		case strings.EqualFold(field, "architecture"):
+// 			archName, _:= osarch.ArchitectureName(container.Architecture())
+// 			result = value == archName
+// 			break
+
+// 		case strings.EqualFold(field, "location"):
+// 			result = value == container.Location()
+// 			break
+
+// 		case strings.EqualFold(field,"status") || strings.EqualFold(field, "state"):
+// 			result = container.State() == value
+// 			break
+
+// 		case strings.EqualFold(field, "project"):
+// 			result = container.Project() == value
+// 			break
+
+// 		case strings.HasPrefix(field, "config"):
+// 			fieldCut := field[7:len(field)]
+// 			config := container.ExpandedConfig()
+// 			result = config[fieldCut] == value
+// 			break
+
+// 		case strings.HasPrefix(field, "device"):
+// 			fieldSplit := strings.Split(field,".")
+// 			valSplit := strings.Split(value,".")
+// 			devices := container.ExpandedDevices()
+// 			dev := devices[valSplit[0]]
+// 			result = dev != nil
+// 			if len(fieldSplit) > 1 && result {
+// 				result = dev[fieldSplit[1]] == valSplit[1]
+// 			}
+// 			break
+
+// 		default:
+// 			return false
+// 	}
+
+// 	if op == "ne" {
+// 		result = !result
+// 	}
+
+// 	return result
+// }
+
 
 func containersGet(d *Daemon, r *http.Request) response.Response {
 	for i := 0; i < 100; i++ {
 		result, err := doContainersGet(d, r)
+
+		// filterStr := r.FormValue("filter")
+		// if filterStr != "" {
+		// 	result = doFilterNew(filterStr, result)
+		// }
+
 		if err == nil {
 			return response.SyncResponse(true, result)
 		}
@@ -204,8 +272,13 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	recursion, err := strconv.Atoi(recursionStr)
 	if err != nil {
 		recursion = 0
+
+		if filterStr != "" {
+			recursion = 1
+		}
 	}
 
+
 	// Parse the project field
 	project := projectParam(r)
 
@@ -274,9 +347,9 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	}
 
 	// Filter the results
-	if filterStr != "" {
-		result = doFilter(filterStr, result, d)
-	}	
+	// if filterStr != "" {
+	// 	result = doFilter(filterStr, result, d)
+	// }	
 
 
 	// Get the data
@@ -415,7 +488,13 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 		sort.Slice(resultList, func(i, j int) bool {
 			return resultList[i].Name < resultList[j].Name
 		})
-
+		if filterStr != "" {
+			intList := make([]interface{}, len(resultList))
+			for i := range resultList {
+			    intList[i] = resultList[i]
+			}
+			return doFilterNew(filterStr, intList), nil
+		}
 		return resultList, nil
 	}
 
@@ -424,6 +503,13 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 		return resultFullList[i].Name < resultFullList[j].Name
 	})
 
+	if filterStr != "" { 
+		intList := make([]interface{}, len(resultFullList))
+		for i := range resultFullList {
+		    intList[i] = resultFullList[i]
+		}
+		return doFilterNew(filterStr, intList), nil
+	}
 	return resultFullList, nil
 }
 
diff --git a/lxd/images.go b/lxd/images.go
index cf989c4011..06d463923c 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -916,12 +916,20 @@ func getImageMetadata(fname string) (*api.ImageMetadata, string, error) {
 	return &result, imageType, nil
 }
 
-func doImagesGet(d *Daemon, recursion bool, project string, public bool) (interface{}, error) {
+func evaluateFieldImage(field string, value string, operator string , obj *api.Image) bool {
+	return true
+}
+
+func doImagesGet(d *Daemon, recursion bool, project string, public bool, filterStr string) (interface{}, error) {
 	results, err := d.cluster.ImagesGet(project, public)
 	if err != nil {
 		return []string{}, err
 	}
 
+	if filterStr != "" {
+		recursion = true
+	}
+
 	resultString := make([]string, len(results))
 	resultMap := make([]*api.Image, len(results))
 	i := 0
@@ -930,7 +938,7 @@ func doImagesGet(d *Daemon, recursion bool, project string, public bool) (interf
 			url := fmt.Sprintf("/%s/images/%s", version.APIVersion, name)
 			resultString[i] = url
 		} else {
-			image, response := doImageGet(d.cluster, project, name, public)
+			image, response := doImageGet(d.cluster, project, name, public, filterStr)
 			if response != nil {
 				continue
 			}
@@ -943,15 +951,22 @@ func doImagesGet(d *Daemon, recursion bool, project string, public bool) (interf
 	if !recursion {
 		return resultString, nil
 	}
-
+	if filterStr != "" { 
+		intList := make([]interface{}, len(resultMap))
+		for i := range resultMap {
+		    intList[i] = resultMap[i]
+		}
+		return doFilterNew(filterStr, intList), nil
+	}
 	return resultMap, nil
 }
 
 func imagesGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
+	filter := r.FormValue("filter")
 	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
 
-	result, err := doImagesGet(d, util.IsRecursionRequest(r), project, public)
+	result, err := doImagesGet(d, util.IsRecursionRequest(r), project, public, filter)
 	if err != nil {
 		return response.SmartError(err)
 	}
@@ -1513,8 +1528,10 @@ func imageDeleteFromDisk(fingerprint string) {
 	}
 }
 
-func doImageGet(db *db.Cluster, project, fingerprint string, public bool) (*api.Image, response.Response) {
+func doImageGet(db *db.Cluster, project, fingerprint string, public bool, filter string) (*api.Image, response.Response) {
 	_, imgInfo, err := db.ImageGet(project, fingerprint, public, false)
+
+	logger.Warnf("############Jackieerror: %s", imgInfo)
 	if err != nil {
 		return nil, response.SmartError(err)
 	}
@@ -1557,8 +1574,9 @@ func imageGet(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
 	secret := r.FormValue("secret")
+	filter := r.FormValue("filter")
 
-	info, resp := doImageGet(d.cluster, project, fingerprint, false)
+	info, resp := doImageGet(d.cluster, project, fingerprint, false, filter)
 	if resp != nil {
 		return resp
 	}

From aaa461416804b41b6f10eb01559455d4f8c94fc1 Mon Sep 17 00:00:00 2001
From: Jacqueline Deans <jdeans289 at gmail.com>
Date: Mon, 9 Dec 2019 11:20:06 -0600
Subject: [PATCH 07/13] lxd: Expanded filtering for images

---
 lxd/api.go    |  8 +++--
 lxd/images.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 86 insertions(+), 5 deletions(-)

diff --git a/lxd/api.go b/lxd/api.go
index d4abf0dfc8..285253197d 100644
--- a/lxd/api.go
+++ b/lxd/api.go
@@ -173,7 +173,7 @@ func applyFilterNew (fstr string, obj interface{}) bool {
 	queryLen := len(filterSplit)
 
 	for index < queryLen {
-		if (filterSplit[index] == "not") {
+		if strings.EqualFold(filterSplit[index], "not") {
 			not = true
 			index++
 		}
@@ -208,10 +208,12 @@ func applyFilterNew (fstr string, obj interface{}) bool {
 			curResult = !curResult
 		}
 
-		if prevLogical == "and" {
+		if strings.EqualFold (prevLogical, "and") {
 			result = curResult && result
 		} else {
-			result = curResult || result
+			if strings.EqualFold(prevLogical, "or") {
+				result = curResult || result
+			}
 		}
 
 		if index < queryLen {
diff --git a/lxd/images.go b/lxd/images.go
index 06d463923c..557cb7b663 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -916,8 +916,87 @@ func getImageMetadata(fname string) (*api.ImageMetadata, string, error) {
 	return &result, imageType, nil
 }
 
-func evaluateFieldImage(field string, value string, operator string , obj *api.Image) bool {
-	return true
+func evaluateFieldImage(field string, value string, op string , image *api.Image) bool {
+	result := false
+
+	logger.Warnf("IN EVAL IMAGE: %s", image)
+
+	switch {
+		case strings.EqualFold(field, "architecture"):
+			result = value == image.Architecture
+			break
+
+		case strings.EqualFold(field, "filename"):
+			result = value == image.Filename
+			break
+
+		case strings.EqualFold(field,"type"):
+			result = image.Type == value
+			break
+
+		case strings.EqualFold(field,"fingerprint"):
+			result = image.Fingerprint == value
+			break
+
+		case strings.EqualFold(field,"public"):
+			imagePut := image.ImagePut
+			if strings.EqualFold(value,"true"){
+				result = imagePut.Public
+			} else {
+				result = !imagePut.Public
+			}
+			break
+
+		case strings.EqualFold(field,"autoupdate"):
+			imagePut := image.ImagePut
+			if strings.EqualFold(value,"true"){
+				result = imagePut.AutoUpdate
+			} else {
+				result = !imagePut.AutoUpdate
+			}
+			break
+
+		case strings.HasPrefix(field, "UpdateSource"):
+			fieldCut := field[13:len(field)]
+			source := image.UpdateSource
+			switch (fieldCut) {
+				case "Alias":
+					result = source.Alias == value
+					break
+				case "Certificate":
+					result = source.Certificate == value
+					break
+				case "Protocol":
+					result = source.Protocol == value
+					break
+				case "Server":
+					result = source.Server == value
+					break
+				case "ImageType":
+					result = source.ImageType == value
+					break
+				default:
+					result = false
+					break
+			}
+			
+			break
+
+		case strings.HasPrefix(field, "Properties"):
+			fieldCut := field[11:len(field)]
+			imagePut := image.ImagePut
+			result = value == imagePut.Properties[fieldCut]
+			break
+
+		default:
+			return false
+	}
+
+	if strings.EqualFold(op, "ne") {
+		result = !result
+	}
+
+	return result
 }
 
 func doImagesGet(d *Daemon, recursion bool, project string, public bool, filterStr string) (interface{}, error) {

From 31121f0173fdc230d29910f5b5bd00c288d51d7e Mon Sep 17 00:00:00 2001
From: Jenna Chenette <jen.chen18 at yahoo.com>
Date: Wed, 11 Dec 2019 12:36:54 -0600
Subject: [PATCH 08/13] Added filtering to the documentation

---
 doc/rest-api.md | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 0d21286740..076f293506 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -149,6 +149,25 @@ they point to (typically a dict).
 Recursion is implemented by simply replacing any pointer to an job (URL)
 by the object itself.
 
+## Filter
+To filter your results on certain values, filter is implemented for collections.
+A `filter` argument can be passed to a GET query against a collection.
+
+There is no default value for filter which means that all results found will
+be returned. The following is the language used for the filter argument:
+
+?filter=field_name+eq+desired_field_assignment
+
+The language follows the OData conventions for structuring REST API filtering
+logic. Logical operators are also supported for filtering: not(not), equals(eq),
+not equals(ne), and(and), or(or). Nesting filtering is also supported. For
+instance to filter on a field in a device config you would pass:
+
+?filter=config.field_name+eq+desired_field_assignment
+
+Filter is implemented by simply finding the results returned by a normal GET
+query and only returning the ones that match the information given.
+
 ## Async operations
 Any operation which may take more than a second to be done must be done
 in the background, returning a background operation ID to the client.

From 969522af933a9cca37bca46677823888179e732e Mon Sep 17 00:00:00 2001
From: Jenna Chenette <jen.chen18 at yahoo.com>
Date: Wed, 11 Dec 2019 12:56:08 -0600
Subject: [PATCH 09/13] Filter code cleanup

---
 lxd/api.go            |   6 +-
 lxd/containers_get.go | 146 +-----------------------------------------
 lxd/images.go         |   2 +-
 3 files changed, 7 insertions(+), 147 deletions(-)

diff --git a/lxd/api.go b/lxd/api.go
index 285253197d..b1fcb5d09e 100644
--- a/lxd/api.go
+++ b/lxd/api.go
@@ -152,17 +152,17 @@ func queryParam(request *http.Request, key string) string {
 	return values.Get(key)
 }
 
-func doFilterNew (fstr string, result []interface{}) []interface{} {
+func doFilter (fstr string, result []interface{}) []interface{} {
 	newResult := result[:0]
 	for _,obj := range result {
-		if applyFilterNew(fstr, obj) {
+		if applyFilter(fstr, obj) {
 			newResult = append(newResult, obj)
 		}
 	}
 	return newResult
 }
 
-func applyFilterNew (fstr string, obj interface{}) bool {
+func applyFilter (fstr string, obj interface{}) bool {
 	filterSplit := strings.Fields(fstr)
 
 	index := 0
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 5f3641200d..f5530af3bb 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -44,81 +44,6 @@ func urlInstanceTypeDetect(r *http.Request) (instancetype.Type, error) {
 	return instancetype.Any, nil
 }
 
-// func doFilterNew (fstr string, result []interface{}) []interface{} {
-// 	newResult := result[:0]
-// 	for _,obj := range result {
-// 		if applyFilterNew(fstr, obj) {
-// 			newResult = append(newResult, obj)
-// 		}
-// 	}
-// 	return newResult
-// }
-
-// func doFilter (fstr string, result map[string][]string, d *Daemon) map[string][]string {
-// 	newResult := map[string][]string{}
-// 	for address, containers := range result {
-// 		newContainers := []string{}
-// 		for _,container := range containers {
-// 			inst, err := instanceLoadByProjectAndName(d.State(), address, container)
-
-// 			if err != nil {
-// 				continue
-// 			}
-
-// 			if applyFilter(fstr, inst) {
-// 				newContainers = append(newContainers, container)
-// 			}
-// 		}
-
-// 		newResult[address] = newContainers
-// 	}
-
-// 	return newResult
-// }
-
-// func applyFilter (fstr string, container instance.Instance) bool {
-// 	filterSplit := strings.Fields(fstr)
-
-// 	index := 0
-// 	result := true
-// 	prevLogical := "and"
-// 	not := false
-
-// 	queryLen := len(filterSplit)
-
-// 	for index < queryLen {
-// 		if (filterSplit[index] == "not") {
-// 			not = true
-// 			index++
-// 		}
-// 		field := filterSplit[index]
-// 		operator := filterSplit[index+1]
-// 		value := filterSplit[index+2]
-// 		index+=3
-
-// 		// eval 
-// 		curResult := evaluateField(field, value, operator, container);
-
-// 		if not {
-// 			not = false
-// 			curResult = !curResult
-// 		}
-
-// 		if prevLogical == "and" {
-// 			result = curResult && result
-// 		} else {
-// 			result = curResult || result
-// 		}
-
-// 		if index < queryLen {
-// 			prevLogical = filterSplit[index]
-// 			index++
-// 		}
-// 	}
-
-// 	return result
-// }
-
 func evaluateFieldInstanceFull(field string, value string, op string, instFull *api.InstanceFull) bool {
 	return evaluateFieldInstance(field, value, op, &instFull.Instance)
 }
@@ -172,69 +97,10 @@ func evaluateFieldInstance(field string, value string, op string, container *api
 	return result
 }
 
-// func evaluateField (field string, value string, op string, container instance.Instance) bool {
-// 	result := false
-
-// 	switch {
-// 		case strings.EqualFold(field, "name"):
-// 			result = value == container.Name()
-// 			break
-
-// 		case strings.EqualFold(field, "architecture"):
-// 			archName, _:= osarch.ArchitectureName(container.Architecture())
-// 			result = value == archName
-// 			break
-
-// 		case strings.EqualFold(field, "location"):
-// 			result = value == container.Location()
-// 			break
-
-// 		case strings.EqualFold(field,"status") || strings.EqualFold(field, "state"):
-// 			result = container.State() == value
-// 			break
-
-// 		case strings.EqualFold(field, "project"):
-// 			result = container.Project() == value
-// 			break
-
-// 		case strings.HasPrefix(field, "config"):
-// 			fieldCut := field[7:len(field)]
-// 			config := container.ExpandedConfig()
-// 			result = config[fieldCut] == value
-// 			break
-
-// 		case strings.HasPrefix(field, "device"):
-// 			fieldSplit := strings.Split(field,".")
-// 			valSplit := strings.Split(value,".")
-// 			devices := container.ExpandedDevices()
-// 			dev := devices[valSplit[0]]
-// 			result = dev != nil
-// 			if len(fieldSplit) > 1 && result {
-// 				result = dev[fieldSplit[1]] == valSplit[1]
-// 			}
-// 			break
-
-// 		default:
-// 			return false
-// 	}
-
-// 	if op == "ne" {
-// 		result = !result
-// 	}
-
-// 	return result
-// }
-
-
 func containersGet(d *Daemon, r *http.Request) response.Response {
 	for i := 0; i < 100; i++ {
 		result, err := doContainersGet(d, r)
 
-		// filterStr := r.FormValue("filter")
-		// if filterStr != "" {
-		// 	result = doFilterNew(filterStr, result)
-		// }
-
 		if err == nil {
 			return response.SyncResponse(true, result)
 		}
@@ -344,13 +210,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 		resultMu.Lock()
 		resultFullList = append(resultFullList, &c)
 		resultMu.Unlock()
-	}
-
-	// Filter the results
-	// if filterStr != "" {
-	// 	result = doFilter(filterStr, result, d)
-	// }	
-
+	}	
 
 	// Get the data
 	wg := sync.WaitGroup{}
@@ -493,7 +353,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 			for i := range resultList {
 			    intList[i] = resultList[i]
 			}
-			return doFilterNew(filterStr, intList), nil
+			return doFilter(filterStr, intList), nil
 		}
 		return resultList, nil
 	}
@@ -508,7 +368,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 		for i := range resultFullList {
 		    intList[i] = resultFullList[i]
 		}
-		return doFilterNew(filterStr, intList), nil
+		return doFilter(filterStr, intList), nil
 	}
 	return resultFullList, nil
 }
diff --git a/lxd/images.go b/lxd/images.go
index 557cb7b663..b99f90b627 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1035,7 +1035,7 @@ func doImagesGet(d *Daemon, recursion bool, project string, public bool, filterS
 		for i := range resultMap {
 		    intList[i] = resultMap[i]
 		}
-		return doFilterNew(filterStr, intList), nil
+		return doFilter(filterStr, intList), nil
 	}
 	return resultMap, nil
 }

From 950ef7be3d6c18153e99f5cfd4bd700482fe50ae Mon Sep 17 00:00:00 2001
From: Jenna Chenette <jen.chen18 at yahoo.com>
Date: Wed, 11 Dec 2019 13:47:39 -0600
Subject: [PATCH 10/13] Added more info to API filter documentation

---
 doc/rest-api.md | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 076f293506..bf622742c4 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -153,6 +153,8 @@ by the object itself.
 To filter your results on certain values, filter is implemented for collections.
 A `filter` argument can be passed to a GET query against a collection.
 
+FIltering is available for the container and image endpoints.
+
 There is no default value for filter which means that all results found will
 be returned. The following is the language used for the filter argument:
 
@@ -165,6 +167,15 @@ instance to filter on a field in a device config you would pass:
 
 ?filter=config.field_name+eq+desired_field_assignment
 
+For filtering on device attributes you would pass:
+
+?filter=device.field_name+eq+device_name.desired_field_assignment
+
+Here are a few GET query examples of the different filtering methods mentioned above:
+curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=name+eq+first+and+status+eq+Running
+curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=config.image.os+eq+ubuntu+and+device.nictype+eq+eth0.bridged
+curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/images?filter=Properties.os+eq+Centos+and+UpdateSource.Protocol+eq+simplestreams
+
 Filter is implemented by simply finding the results returned by a normal GET
 query and only returning the ones that match the information given.
 

From c606b73aa448ffa86ae3459334cdae58af65bccc Mon Sep 17 00:00:00 2001
From: Jenna Chenette <jen.chen18 at yahoo.com>
Date: Wed, 11 Dec 2019 13:49:03 -0600
Subject: [PATCH 11/13] Added more info to API filter documentation

---
 doc/rest-api.md | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index bf622742c4..d0dea369a9 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -172,9 +172,10 @@ For filtering on device attributes you would pass:
 ?filter=device.field_name+eq+device_name.desired_field_assignment
 
 Here are a few GET query examples of the different filtering methods mentioned above:
-curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=name+eq+first+and+status+eq+Running
-curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=config.image.os+eq+ubuntu+and+device.nictype+eq+eth0.bridged
-curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/images?filter=Properties.os+eq+Centos+and+UpdateSource.Protocol+eq+simplestreams
+
+* [`/`]curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=name+eq+first+and+status+eq+Running
+* [`/`]curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=config.image.os+eq+ubuntu+and+device.nictype+eq+eth0.bridged
+* [`/`]curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/images?filter=Properties.os+eq+Centos+and+UpdateSource.Protocol+eq+simplestreams
 
 Filter is implemented by simply finding the results returned by a normal GET
 query and only returning the ones that match the information given.

From 422f7b535cc360d6ce535cb6c45b232d1049f4d1 Mon Sep 17 00:00:00 2001
From: Jenna Chenette <jen.chen18 at yahoo.com>
Date: Wed, 11 Dec 2019 13:49:45 -0600
Subject: [PATCH 12/13] Filter code cleanup

---
 doc/rest-api.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index d0dea369a9..2a5c99fdee 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -173,9 +173,9 @@ For filtering on device attributes you would pass:
 
 Here are a few GET query examples of the different filtering methods mentioned above:
 
-* [`/`]curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=name+eq+first+and+status+eq+Running
-* [`/`]curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=config.image.os+eq+ubuntu+and+device.nictype+eq+eth0.bridged
-* [`/`]curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/images?filter=Properties.os+eq+Centos+and+UpdateSource.Protocol+eq+simplestreams
+* curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=name+eq+first+and+status+eq+Running
+* curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=config.image.os+eq+ubuntu+and+device.nictype+eq+eth0.bridged
+* curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/images?filter=Properties.os+eq+Centos+and+UpdateSource.Protocol+eq+simplestreams
 
 Filter is implemented by simply finding the results returned by a normal GET
 query and only returning the ones that match the information given.

From 77ce74bdf7cb3845c3fbb05dc58ffb6118a1ad1f Mon Sep 17 00:00:00 2001
From: Jacqueline Deans <jdeans289 at gmail.com>
Date: Thu, 12 Dec 2019 00:48:44 -0600
Subject: [PATCH 13/13] Added support for quoted strings, updated docs

---
 doc/rest-api.md       | 21 ++++++++++++---------
 lxd/api.go            | 22 ++++++++++++++++++----
 lxd/containers_get.go |  3 +++
 3 files changed, 33 insertions(+), 13 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 2a5c99fdee..51b26db61c 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -153,29 +153,32 @@ by the object itself.
 To filter your results on certain values, filter is implemented for collections.
 A `filter` argument can be passed to a GET query against a collection.
 
-FIltering is available for the container and image endpoints.
+FIltering is available for the instance and image endpoints.
 
 There is no default value for filter which means that all results found will
 be returned. The following is the language used for the filter argument:
 
-?filter=field_name+eq+desired_field_assignment
+?filter=field_name eq desired_field_assignment
 
 The language follows the OData conventions for structuring REST API filtering
 logic. Logical operators are also supported for filtering: not(not), equals(eq),
-not equals(ne), and(and), or(or). Nesting filtering is also supported. For
-instance to filter on a field in a device config you would pass:
+not equals(ne), and(and), or(or). Values with spaces can be surrounded with quotes.
+Nesting filtering is also supported. For instance, to filter on a field in a 
+device config you would pass:
 
-?filter=config.field_name+eq+desired_field_assignment
+?filter=config.field_name eq desired_field_assignment
 
 For filtering on device attributes you would pass:
 
-?filter=device.field_name+eq+device_name.desired_field_assignment
+?filter=device.field_name eq device_name.desired_field_assignment
 
 Here are a few GET query examples of the different filtering methods mentioned above:
 
-* curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=name+eq+first+and+status+eq+Running
-* curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/containers?filter=config.image.os+eq+ubuntu+and+device.nictype+eq+eth0.bridged
-* curl --unix-socket /var/lib/lxd/unix.socket -X GET lxd/1.0/images?filter=Properties.os+eq+Centos+and+UpdateSource.Protocol+eq+simplestreams
+containers?filter=name eq "my container" and status eq Running
+
+containers?filter=config.image.os eq ubuntu or device.nictype eq eth0.bridged
+
+images?filter=Properties.os eq Centos and not UpdateSource.Protocol eq simplestreams
 
 Filter is implemented by simply finding the results returned by a normal GET
 query and only returning the ones that match the information given.
diff --git a/lxd/api.go b/lxd/api.go
index b1fcb5d09e..18c8ccea93 100644
--- a/lxd/api.go
+++ b/lxd/api.go
@@ -152,6 +152,7 @@ func queryParam(request *http.Request, key string) string {
 	return values.Get(key)
 }
 
+// Process a filter over a set about to be returned
 func doFilter (fstr string, result []interface{}) []interface{} {
 	newResult := result[:0]
 	for _,obj := range result {
@@ -162,6 +163,7 @@ func doFilter (fstr string, result []interface{}) []interface{} {
 	return newResult
 }
 
+// Apply a filter to a single object
 func applyFilter (fstr string, obj interface{}) bool {
 	filterSplit := strings.Fields(fstr)
 
@@ -181,11 +183,22 @@ func applyFilter (fstr string, obj interface{}) bool {
 		operator := filterSplit[index+1]
 		value := filterSplit[index+2]
 		index+=3
-		// eval 
+
+		// support strings with spaces that are quoted
+		if strings.HasPrefix(value, "\"") {
+			value = value[1:len(value)]
+			for !strings.HasSuffix(filterSplit[index], "\"") {
+				value = value + " " + filterSplit[index]
+				index++
+			}
+			end := filterSplit[index]
+			value = value + " " + end[0:len(end)-1]
+			index++
+		}
 
 		curResult := false
 		
-		logger.Warnf("JackieError: %s", reflect.TypeOf(obj))
+		// Pass to eval function of correct type
 		objType := reflect.TypeOf(obj).String()
 		switch (objType) {
 			case "*api.Instance":
@@ -198,11 +211,12 @@ func applyFilter (fstr string, obj interface{}) bool {
 				curResult = evaluateFieldImage(field, value, operator, obj.(*api.Image))
 				break
 			default:
-				logger.Warnf("Unable to identify type")
+				logger.Error("Error while filtering: unable to identify type")
 				break
-
 		}
 
+
+		// Finish out logic
 		if not {
 			not = false
 			curResult = !curResult
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index f5530af3bb..56aa34b99d 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -44,10 +44,13 @@ func urlInstanceTypeDetect(r *http.Request) (instancetype.Type, error) {
 	return instancetype.Any, nil
 }
 
+// Pull out the Instance in order to filter
 func evaluateFieldInstanceFull(field string, value string, op string, instFull *api.InstanceFull) bool {
 	return evaluateFieldInstance(field, value, op, &instFull.Instance)
 }
 
+
+// Evaluate a field of a filter on an instance
 func evaluateFieldInstance(field string, value string, op string, container *api.Instance) bool {
 	result := false
 


More information about the lxc-devel mailing list