Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API: Add support for fetching images across all projects (from Incus) #14260

2 changes: 2 additions & 0 deletions client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ type InstanceServer interface {
UpdateImageAlias(name string, alias api.ImageAliasesEntryPut, ETag string) (err error)
RenameImageAlias(name string, alias api.ImageAliasesEntryPost) (err error)
DeleteImageAlias(name string) (err error)
GetImagesAllProjects() (images []api.Image, err error)
GetImagesAllProjectsWithFilter(filters []string) (images []api.Image, err error)

// Network functions ("network" API extension)
GetNetworkNames() (names []string, err error)
Expand Down
41 changes: 41 additions & 0 deletions client/lxd_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,47 @@ func (r *ProtocolLXD) GetImages() ([]api.Image, error) {
return images, nil
}

// GetImagesAllProjects returns a list of images across all projects as Image structs.
func (r *ProtocolLXD) GetImagesAllProjects() ([]api.Image, error) {
images := []api.Image{}

err := r.CheckExtension("images_all_projects")
if err != nil {
return nil, err
}

u := api.NewURL().Path("images").WithQuery("recursion", "1").WithQuery("all-projects", "true")
_, err = r.queryStruct("GET", u.String(), nil, "", &images)
if err != nil {
return nil, err
}

return images, nil
}

// GetImagesAllProjectsWithFilter returns a filtered list of images across all projects as Image structs.
func (r *ProtocolLXD) GetImagesAllProjectsWithFilter(filters []string) ([]api.Image, error) {
err := r.CheckExtension("api_filtering")
if err != nil {
return nil, err
}

images := []api.Image{}

err = r.CheckExtension("images_all_projects")
if err != nil {
return nil, err
}

u := api.NewURL().Path("images").WithQuery("recursion", "1").WithQuery("all-projects", "true").WithQuery("filter", parseFilters(filters))
_, err = r.queryStruct("GET", u.String(), nil, "", &images)
if err != nil {
return nil, err
}

return images, nil
}

// GetImagesWithFilter returns a filtered list of available images as Image structs.
func (r *ProtocolLXD) GetImagesWithFilter(filters []string) ([]api.Image, error) {
err := r.CheckExtension("api_filtering")
Expand Down
4 changes: 4 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2519,3 +2519,7 @@ Adds a new {config:option}`instance-resource-limits:limits.cpu.pin_strategy` con
## `gpu_cdi`

Adds support for using the Container Device Interface (CDI) specification to configure GPU passthrough in LXD containers. The `id` field of GPU devices now accepts CDI identifiers (for example, `{VENDOR_DOMAIN_NAME}/gpu=gpu{INDEX}`) for containers, in addition to DRM card IDs. This enables GPU passthrough for devices that don't use PCI addressing (like NVIDIA Tegra iGPUs) and provides a more flexible way to identify and configure GPU devices.

## `images_all_projects`

This adds support for listing images across all projects using the `all-projects` parameter in `GET /1.0/images` requests.
22 changes: 22 additions & 0 deletions doc/rest-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,11 @@ definitions:
type: string
type: array
x-go-name: Profiles
project:
description: Project name
example: project1
type: string
x-go-name: Project
properties:
additionalProperties:
type: string
Expand Down Expand Up @@ -9019,6 +9024,10 @@ paths:
in: query
name: filter
type: string
- description: Retrieve images from all projects
in: query
name: all-projects
type: boolean
produces:
- application/json
responses:
Expand Down Expand Up @@ -9770,6 +9779,10 @@ paths:
in: query
name: filter
type: string
- description: Retrieve images from all projects
in: query
name: all-projects
type: boolean
produces:
- application/json
responses:
Expand Down Expand Up @@ -9858,6 +9871,10 @@ paths:
in: query
name: filter
type: string
- description: Retrieve images from all projects
in: query
name: all-projects
type: boolean
produces:
- application/json
responses:
Expand Down Expand Up @@ -9906,6 +9923,11 @@ paths:
in: query
name: filter
type: string
- description: Retrieve images from all projects
example: default
in: query
name: all-projects
type: boolean
produces:
- application/json
responses:
Expand Down
63 changes: 48 additions & 15 deletions lxc/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -1078,8 +1078,9 @@ type cmdImageList struct {
global *cmdGlobal
image *cmdImage

flagFormat string
flagColumns string
flagFormat string
flagColumns string
flagAllProjects bool
}

func (c *cmdImageList) command() *cobra.Command {
Expand Down Expand Up @@ -1107,13 +1108,15 @@ Column shorthand chars:
F - Fingerprint (long)
p - Whether image is public
d - Description
e - Project
a - Architecture
s - Size
u - Upload date
t - Type`))

cmd.Flags().StringVarP(&c.flagColumns, "columns", "c", "lfpdatsu", i18n.G("Columns")+"``")
cmd.Flags().StringVarP(&c.flagColumns, "columns", "c", defaultImagesColumns, i18n.G("Columns")+"``")
cmd.Flags().StringVarP(&c.flagFormat, "format", "f", "table", i18n.G("Format (csv|json|table|yaml|compact)")+"``")
cmd.Flags().BoolVar(&c.flagAllProjects, "all-projects", false, i18n.G("Display images from all projects"))
cmd.RunE = c.run

cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
Expand All @@ -1127,23 +1130,32 @@ Column shorthand chars:
return cmd
}

const defaultImagesColumns = "lfpdatsu"
const defaultImagesColumnsAllProjects = "elfpdatsu"

func (c *cmdImageList) parseColumns() ([]imageColumn, error) {
columnsShorthandMap := map[rune]imageColumn{
'l': {i18n.G("ALIAS"), c.aliasColumnData},
'L': {i18n.G("ALIASES"), c.aliasesColumnData},
'a': {i18n.G("ARCHITECTURE"), c.architectureColumnData},
'd': {i18n.G("DESCRIPTION"), c.descriptionColumnData},
'e': {i18n.G("PROJECT"), c.projectColumnData},
'f': {i18n.G("FINGERPRINT"), c.fingerprintColumnData},
'F': {i18n.G("FINGERPRINT"), c.fingerprintFullColumnData},
'l': {i18n.G("ALIAS"), c.aliasColumnData},
'L': {i18n.G("ALIASES"), c.aliasesColumnData},
'p': {i18n.G("PUBLIC"), c.publicColumnData},
'd': {i18n.G("DESCRIPTION"), c.descriptionColumnData},
'a': {i18n.G("ARCHITECTURE"), c.architectureColumnData},
's': {i18n.G("SIZE"), c.sizeColumnData},
'u': {i18n.G("UPLOAD DATE"), c.uploadDateColumnData},
't': {i18n.G("TYPE"), c.typeColumnData},
'u': {i18n.G("UPLOAD DATE"), c.uploadDateColumnData},
}

columnList := strings.Split(c.flagColumns, ",")
// Add project column if --all-projects flag specified and custom columns are not specified.
if c.flagAllProjects && c.flagColumns == defaultImagesColumns {
c.flagColumns = defaultImagesColumnsAllProjects
}

columnList := strings.Split(c.flagColumns, ",")
columns := []imageColumn{}

for _, columnEntry := range columnList {
if columnEntry == "" {
return nil, fmt.Errorf(i18n.G("Empty column entry (redundant, leading or trailing command) in '%s'"), c.flagColumns)
Expand Down Expand Up @@ -1201,6 +1213,10 @@ func (c *cmdImageList) descriptionColumnData(image api.Image) string {
return c.findDescription(image.Properties)
}

func (c *cmdImageList) projectColumnData(image api.Image) string {
return image.Project
}

func (c *cmdImageList) architectureColumnData(image api.Image) string {
return image.Architecture
}
Expand Down Expand Up @@ -1337,6 +1353,11 @@ func (c *cmdImageList) run(cmd *cobra.Command, args []string) error {
return err
}

d, err := c.global.conf.GetInstanceServer(remoteName)
if err != nil {
return err
}

// Process the filters
filters := []string{}
if name != "" {
Expand All @@ -1355,15 +1376,27 @@ func (c *cmdImageList) run(cmd *cobra.Command, args []string) error {

serverFilters, clientFilters := getServerSupportedFilters(filters, api.Image{})

var images []api.Image
allImages, err := remoteServer.GetImagesWithFilter(serverFilters)
if err != nil {
allImages, err = remoteServer.GetImages()
var allImages, images []api.Image
if c.flagAllProjects {
allImages, err = d.GetImagesAllProjectsWithFilter(serverFilters)
if err != nil {
return err
allImages, err = d.GetImagesAllProjects()
if err != nil {
return err
}

clientFilters = filters
}
} else {
allImages, err = remoteServer.GetImagesWithFilter(serverFilters)
if err != nil {
allImages, err = remoteServer.GetImages()
if err != nil {
return err
}

clientFilters = filters
clientFilters = filters
}
}

for _, image := range allImages {
Expand Down
1 change: 1 addition & 0 deletions lxd/db/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ func (c *ClusterTx) GetImageByFingerprintPrefix(ctx context.Context, fingerprint
image.Cached = object.Cached
image.Public = object.Public
image.AutoUpdate = object.AutoUpdate
image.Project = object.Project

err = c.imageFill(
ctx, object.ID, &image,
Expand Down
Loading
Loading