1
0
mirror of https://github.com/lxc/incus.git synced 2026-02-05 09:46:19 +01:00

Merge pull request #2015 from rahafjrw/filter_incus_project_list

Add server-side filtering for `incus project list`
This commit is contained in:
Stéphane Graber
2025-04-28 22:23:08 -04:00
committed by GitHub
3 changed files with 65 additions and 2 deletions

View File

@@ -44,6 +44,26 @@ func (r *ProtocolIncus) GetProjects() ([]api.Project, error) {
return projects, nil
}
// GetProjectsWithFilter returns a filtered list of projects as Project structs.
func (r *ProtocolIncus) GetProjectsWithFilter(filters []string) ([]api.Project, error) {
if !r.HasExtension("projects") {
return nil, fmt.Errorf("The server is missing the required \"projects\" API extension")
}
projects := []api.Project{}
v := url.Values{}
v.Set("recursion", "1")
v.Set("filter", parseFilters(filters))
_, err := r.queryStruct("GET", fmt.Sprintf("/projects?%s", v.Encode()), nil, "", &projects)
if err != nil {
return nil, err
}
return projects, nil
}
// GetProject returns a Project entry for the provided name.
func (r *ProtocolIncus) GetProject(name string) (*api.Project, string, error) {
if !r.HasExtension("projects") {

View File

@@ -305,6 +305,7 @@ type InstanceServer interface {
// Project functions
GetProjectNames() (names []string, err error)
GetProjects() (projects []api.Project, err error)
GetProjectsWithFilter(filters []string) (projects []api.Project, err error)
GetProject(name string) (project *api.Project, ETag string, err error)
GetProjectState(name string) (project *api.ProjectState, err error)
GetProjectAccess(name string) (access api.Access, err error)

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"reflect"
"slices"
"sort"
"strings"
@@ -523,7 +524,7 @@ type cmdProjectList struct {
// Command returns a cobra.Command for use with (*cobra.Command).AddCommand.
func (c *cmdProjectList) Command() *cobra.Command {
cmd := &cobra.Command{}
cmd.Use = usage("list", i18n.G("[<remote>:]"))
cmd.Use = usage("list", i18n.G("[<remote>:] [<filter>...]"))
cmd.Aliases = []string{"ls"}
cmd.Short = i18n.G("List projects")
cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
@@ -692,8 +693,16 @@ func (c *cmdProjectList) Run(cmd *cobra.Command, args []string) error {
resource := resources[0]
// Process the filters
filters := []string{}
if len(args) > 1 {
filters = append(filters, args[1:]...)
}
filters = prepareProjectServerFilters(filters, api.Project{})
// List projects
projects, err := resource.server.GetProjects()
projects, err := resource.server.GetProjectsWithFilter(filters)
if err != nil {
return err
}
@@ -1231,3 +1240,36 @@ func (c *cmdProjectGetCurrent) Run(cmd *cobra.Command, args []string) error {
return nil
}
// prepareProjectServerFilter processes and formats filter criteria
// for projects, ensuring they are in a format that the server can interpret.
func prepareProjectServerFilters(filters []string, i any) []string {
formattedFilters := []string{}
for _, filter := range filters {
membs := strings.SplitN(filter, "=", 2)
key := membs[0]
if len(membs) == 1 {
regexpValue := key
if !strings.Contains(key, "^") && !strings.Contains(key, "$") {
regexpValue = "^" + regexpValue + "$"
}
filter = fmt.Sprintf("name=(%s|^%s.*)", regexpValue, key)
} else {
firstPart := key
if strings.Contains(key, ".") {
firstPart = strings.Split(key, ".")[0]
}
if !structHasField(reflect.TypeOf(i), firstPart) {
filter = fmt.Sprintf("config.%s", filter)
}
}
formattedFilters = append(formattedFilters, filter)
}
return formattedFilters
}