[lxc-devel] [lxd/master] [DRAFT] Define default profile for image #4567

ralubis on Github lxc-bot at linuxcontainers.org
Fri Nov 29 22:26:23 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 779 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191129/a877b269/attachment-0001.bin>
-------------- next part --------------
From e171c7844a3f8bd3e326a2fe18c990b8c2b88a87 Mon Sep 17 00:00:00 2001
From: Jack Stenglein <jackstenglein at utexas.edu>
Date: Sun, 24 Nov 2019 18:08:54 -0600
Subject: [PATCH 1/4] api: Add image_profiles extension

Signed-off-by: Rizwan Lubis <rizwan.lubis at gmail.com>
Signed-off-by: Jack Stenglein <jackstenglein at gmail.com>
---
 doc/api-extensions.md | 3 +++
 shared/version/api.go | 1 +
 2 files changed, 4 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 1b836a0623..632ce186ac 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -886,3 +886,6 @@ This allows for existing a CEPH RDB or FS to be directly connected to a LXD cont
 
 ## virtual\_machines
 Add virtual machine support.
+
+## image\_profiles
+Allows a list of profiles to be applied to an image when launching a new container. 
diff --git a/shared/version/api.go b/shared/version/api.go
index 1afdc1b2d0..7a2602b1b5 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -179,6 +179,7 @@ var APIExtensions = []string{
 	"container_syscall_intercept_mount_fuse",
 	"container_disk_ceph",
 	"virtual-machines",
+	"image_profiles"
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 57c49ee07753922e239c76f0d60ecc0d7e647819 Mon Sep 17 00:00:00 2001
From: Jack Stenglein <jackstenglein at utexas.edu>
Date: Sun, 24 Nov 2019 18:15:17 -0600
Subject: [PATCH 2/4] shared/api: Add image profiles

Signed-off-by: Rizwan Lubis <rizwan.lubis at gmail.com>
Signed-off-by: Jack Stenglein <jackstenglein at gmail.com>
---
 shared/api/image.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/shared/api/image.go b/shared/api/image.go
index 0eb4c392e6..dc48850c4b 100644
--- a/shared/api/image.go
+++ b/shared/api/image.go
@@ -44,6 +44,9 @@ type ImagePut struct {
 
 	// API extension: images_expiry
 	ExpiresAt time.Time `json:"expires_at" yaml:"expires_at"`
+	
+	// API extension: image_profiles
+	Profiles []string `json:"profiles" yaml:"profiles"`
 }
 
 // Image represents a LXD image

From d224caf8862dc0aab16e680e0a17c0b6edf457cf Mon Sep 17 00:00:00 2001
From: Jack Stenglein <jackstenglein at utexas.edu>
Date: Sun, 24 Nov 2019 18:52:14 -0600
Subject: [PATCH 3/4] lxd/db: Add images_profiles table

Signed-off-by: Rizwan Lubis <rizwan.lubis at gmail.com>
Signed-off-by: Jack Stenglein <jackstenglein at gmail.com>
---
 lxc/image.go               | 12 ++++++
 lxd/db/cluster/schema.go   |  8 +++-
 lxd/db/cluster/update.go   | 15 +++++++
 lxd/db/images.go           | 38 +++++++++++++++-
 lxd/db/instances.mapper.go | 88 +++++++++++++++++++-------------------
 lxd/images.go              | 20 +++++++--
 shared/version/api.go      |  2 +-
 7 files changed, 131 insertions(+), 52 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 47db1ae9ed..a89e729933 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -406,6 +406,9 @@ func (c *cmdImageEdit) Run(cmd *cobra.Command, args []string) error {
 		newdata := api.ImagePut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
+			if newdata.Profiles == nil {
+				newdata.Profiles = []string{"default"}
+			}
 			err = resource.server.UpdateImage(image, newdata, etag)
 		}
 
@@ -927,6 +930,15 @@ func (c *cmdImageInfo) Run(cmd *cobra.Command, args []string) error {
 		fmt.Printf("    Alias: %s\n", info.UpdateSource.Alias)
 	}
 
+	if len(info.Profiles) == 0 {
+		fmt.Printf(i18n.G("Profiles: ") + "[]\n")
+	} else {
+		fmt.Println(i18n.G("Profiles:"))
+		for _, name := range info.Profiles {
+			fmt.Printf("    - %s\n", name)
+		}
+	}
+
 	return nil
 }
 
diff --git a/lxd/db/cluster/schema.go b/lxd/db/cluster/schema.go
index 33fe82b2e0..db03bbec58 100644
--- a/lxd/db/cluster/schema.go
+++ b/lxd/db/cluster/schema.go
@@ -57,6 +57,12 @@ CREATE TABLE images_nodes (
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
     FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE
 );
+CREATE TABLE images_profiles (
+	image_id INTEGER NOT NULL,
+	profile_id INTEGER NOT NULL,
+	FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
+	FOREIGN KEY (profile_id) REFERENCES profiles (id) ON DELETE CASCADE
+);
 CREATE INDEX images_project_id_idx ON images (project_id);
 CREATE TABLE images_properties (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@@ -487,5 +493,5 @@ CREATE TABLE storage_volumes_config (
     FOREIGN KEY (storage_volume_id) REFERENCES storage_volumes (id) ON DELETE CASCADE
 );
 
-INSERT INTO schema (version, updated_at) VALUES (18, strftime("%s"))
+INSERT INTO schema (version, updated_at) VALUES (19, strftime("%s"))
 `
diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go
index 461672ad67..85d4c91efb 100644
--- a/lxd/db/cluster/update.go
+++ b/lxd/db/cluster/update.go
@@ -53,6 +53,21 @@ var updates = map[int]schema.Update{
 	16: updateFromV15,
 	17: updateFromV16,
 	18: updateFromV17,
+	19: updateFromV18,
+}
+
+// Add images_profiles table
+func updateFromV18(tx *sql.Tx) error {
+	stmts := `
+CREATE TABLE images_profiles (
+	image_id INTEGER NOT NULL,
+	profile_id INTEGER NOT NULL,
+	FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
+	FOREIGN KEY (profile_id) REFERENCES profiles (id) ON DELETE CASCADE
+);
+`
+	_, err := tx.Exec(stmts)
+	return err
 }
 
 // Add nodes_roles table
diff --git a/lxd/db/images.go b/lxd/db/images.go
index 2db6855437..dc9e34878c 100644
--- a/lxd/db/images.go
+++ b/lxd/db/images.go
@@ -8,7 +8,6 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-
 	"github.com/lxc/lxd/lxd/db/query"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/shared/api"
@@ -476,6 +475,23 @@ func (c *Cluster) imageFill(id int, image *api.Image, create, expire, used, uplo
 
 	image.Aliases = aliases
 
+	// Get the profiles
+	q = "SELECT name FROM profiles WHERE id IN (SELECT profile_id FROM images_profiles WHERE image_id=?)"
+	inargs = []interface{}{id}
+	outfmt = []interface{}{name}
+	results, err = queryScan(c.db, q, inargs, outfmt)
+	if err != nil {
+		return err
+	}
+
+	profiles := make([]string, 0)
+	for _, r := range results {
+		name = r[0].(string)
+		profiles = append(profiles, name)
+	}
+
+	image.Profiles = profiles
+	
 	_, source, err := c.ImageSourceGet(id)
 	if err == nil {
 		image.UpdateSource = &source
@@ -737,7 +753,7 @@ func (c *Cluster) ImageLastAccessInit(fingerprint string) error {
 }
 
 // ImageUpdate updates the image with the given ID.
-func (c *Cluster) ImageUpdate(id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
+func (c *Cluster) ImageUpdate(id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string, profileIds []int64) error {
 	arch, err := osarch.ArchitectureId(architecture)
 	if err != nil {
 		arch = 0
@@ -783,6 +799,24 @@ func (c *Cluster) ImageUpdate(id int, fname string, sz int64, public bool, autoU
 			}
 		}
 
+		_, err = tx.tx.Exec(`DELETE FROM images_profiles WHERE image_id=?`, id)
+		if err != nil {
+			return err
+		}
+
+		stmt3, err := tx.tx.Prepare(`INSERT INTO images_profiles (image_id, profile_id) VALUES (?, ?)`)
+		if err != nil {
+			return err
+		}
+		defer stmt3.Close()
+
+		for _, profileId := range profileIds {
+			_, err = stmt3.Exec(id, profileId)
+			if err != nil {
+				return err
+			}
+		}
+
 		return nil
 	})
 	return err
diff --git a/lxd/db/instances.mapper.go b/lxd/db/instances.mapper.go
index ce827a8960..171d0aad25 100644
--- a/lxd/db/instances.mapper.go
+++ b/lxd/db/instances.mapper.go
@@ -235,19 +235,19 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
 			filter.Node,
 			filter.Name,
 		}
-	} else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Node"] != nil {
-		stmt = c.stmt(instanceObjectsByProjectAndTypeAndNode)
+	} else if criteria["Project"] != nil && criteria["Name"] != nil && criteria["Node"] != nil {
+		stmt = c.stmt(instanceObjectsByProjectAndNameAndNode)
 		args = []interface{}{
 			filter.Project,
-			filter.Type,
+			filter.Name,
 			filter.Node,
 		}
-	} else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Name"] != nil {
-		stmt = c.stmt(instanceObjectsByProjectAndTypeAndName)
+	} else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Node"] != nil {
+		stmt = c.stmt(instanceObjectsByProjectAndTypeAndNode)
 		args = []interface{}{
 			filter.Project,
 			filter.Type,
-			filter.Name,
+			filter.Node,
 		}
 	} else if criteria["Type"] != nil && criteria["Name"] != nil && criteria["Node"] != nil {
 		stmt = c.stmt(instanceObjectsByTypeAndNameAndNode)
@@ -256,12 +256,12 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
 			filter.Name,
 			filter.Node,
 		}
-	} else if criteria["Project"] != nil && criteria["Name"] != nil && criteria["Node"] != nil {
-		stmt = c.stmt(instanceObjectsByProjectAndNameAndNode)
+	} else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Name"] != nil {
+		stmt = c.stmt(instanceObjectsByProjectAndTypeAndName)
 		args = []interface{}{
 			filter.Project,
+			filter.Type,
 			filter.Name,
-			filter.Node,
 		}
 	} else if criteria["Project"] != nil && criteria["Type"] != nil {
 		stmt = c.stmt(instanceObjectsByProjectAndType)
@@ -275,16 +275,22 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
 			filter.Type,
 			filter.Node,
 		}
+	} else if criteria["Type"] != nil && criteria["Name"] != nil {
+		stmt = c.stmt(instanceObjectsByTypeAndName)
+		args = []interface{}{
+			filter.Type,
+			filter.Name,
+		}
 	} else if criteria["Project"] != nil && criteria["Node"] != nil {
 		stmt = c.stmt(instanceObjectsByProjectAndNode)
 		args = []interface{}{
 			filter.Project,
 			filter.Node,
 		}
-	} else if criteria["Type"] != nil && criteria["Name"] != nil {
-		stmt = c.stmt(instanceObjectsByTypeAndName)
+	} else if criteria["Node"] != nil && criteria["Name"] != nil {
+		stmt = c.stmt(instanceObjectsByNodeAndName)
 		args = []interface{}{
-			filter.Type,
+			filter.Node,
 			filter.Name,
 		}
 	} else if criteria["Project"] != nil && criteria["Name"] != nil {
@@ -293,10 +299,14 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
 			filter.Project,
 			filter.Name,
 		}
-	} else if criteria["Node"] != nil && criteria["Name"] != nil {
-		stmt = c.stmt(instanceObjectsByNodeAndName)
+	} else if criteria["Project"] != nil {
+		stmt = c.stmt(instanceObjectsByProject)
+		args = []interface{}{
+			filter.Project,
+		}
+	} else if criteria["Name"] != nil {
+		stmt = c.stmt(instanceObjectsByName)
 		args = []interface{}{
-			filter.Node,
 			filter.Name,
 		}
 	} else if criteria["Type"] != nil {
@@ -309,16 +319,6 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
 		args = []interface{}{
 			filter.Node,
 		}
-	} else if criteria["Project"] != nil {
-		stmt = c.stmt(instanceObjectsByProject)
-		args = []interface{}{
-			filter.Project,
-		}
-	} else if criteria["Name"] != nil {
-		stmt = c.stmt(instanceObjectsByName)
-		args = []interface{}{
-			filter.Name,
-		}
 	} else {
 		stmt = c.stmt(instanceObjects)
 		args = []interface{}{}
@@ -583,28 +583,28 @@ func (c *ClusterTx) InstanceProfilesRef(filter InstanceFilter) (map[string]map[s
 	var stmt *sql.Stmt
 	var args []interface{}
 
-	if criteria["Project"] != nil && criteria["Name"] != nil {
-		stmt = c.stmt(instanceProfilesRefByProjectAndName)
-		args = []interface{}{
-			filter.Project,
-			filter.Name,
-		}
-	} else if criteria["Project"] != nil && criteria["Node"] != nil {
+	if criteria["Project"] != nil && criteria["Node"] != nil {
 		stmt = c.stmt(instanceProfilesRefByProjectAndNode)
 		args = []interface{}{
 			filter.Project,
 			filter.Node,
 		}
-	} else if criteria["Project"] != nil {
-		stmt = c.stmt(instanceProfilesRefByProject)
+	} else if criteria["Project"] != nil && criteria["Name"] != nil {
+		stmt = c.stmt(instanceProfilesRefByProjectAndName)
 		args = []interface{}{
 			filter.Project,
+			filter.Name,
 		}
 	} else if criteria["Node"] != nil {
 		stmt = c.stmt(instanceProfilesRefByNode)
 		args = []interface{}{
 			filter.Node,
 		}
+	} else if criteria["Project"] != nil {
+		stmt = c.stmt(instanceProfilesRefByProject)
+		args = []interface{}{
+			filter.Project,
+		}
 	} else {
 		stmt = c.stmt(instanceProfilesRef)
 		args = []interface{}{}
@@ -686,16 +686,16 @@ func (c *ClusterTx) InstanceConfigRef(filter InstanceFilter) (map[string]map[str
 			filter.Project,
 			filter.Name,
 		}
-	} else if criteria["Project"] != nil {
-		stmt = c.stmt(instanceConfigRefByProject)
-		args = []interface{}{
-			filter.Project,
-		}
 	} else if criteria["Node"] != nil {
 		stmt = c.stmt(instanceConfigRefByNode)
 		args = []interface{}{
 			filter.Node,
 		}
+	} else if criteria["Project"] != nil {
+		stmt = c.stmt(instanceConfigRefByProject)
+		args = []interface{}{
+			filter.Project,
+		}
 	} else {
 		stmt = c.stmt(instanceConfigRef)
 		args = []interface{}{}
@@ -782,16 +782,16 @@ func (c *ClusterTx) InstanceDevicesRef(filter InstanceFilter) (map[string]map[st
 			filter.Project,
 			filter.Node,
 		}
-	} else if criteria["Node"] != nil {
-		stmt = c.stmt(instanceDevicesRefByNode)
-		args = []interface{}{
-			filter.Node,
-		}
 	} else if criteria["Project"] != nil {
 		stmt = c.stmt(instanceDevicesRefByProject)
 		args = []interface{}{
 			filter.Project,
 		}
+	} else if criteria["Node"] != nil {
+		stmt = c.stmt(instanceDevicesRefByNode)
+		args = []interface{}{
+			filter.Node,
+		}
 	} else {
 		stmt = c.stmt(instanceDevicesRef)
 		args = []interface{}{}
diff --git a/lxd/images.go b/lxd/images.go
index cf989c4011..7eb732b8cd 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -347,7 +347,7 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operations.Operation,
 
 	// Update the DB record if needed
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
-		err = d.cluster.ImageUpdate(id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+		err = d.cluster.ImageUpdate(id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, nil)
 		if err != nil {
 			return nil, err
 		}
@@ -415,7 +415,7 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operations.Operation, pro
 	}
 
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
-		err = d.cluster.ImageUpdate(id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+		err = d.cluster.ImageUpdate(id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, nil)
 		if err != nil {
 			return nil, err
 		}
@@ -1597,7 +1597,19 @@ func imagePut(d *Daemon, r *http.Request) response.Response {
 		info.ExpiresAt = req.ExpiresAt
 	}
 
-	err = d.cluster.ImageUpdate(id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties)
+	// Get profile ids
+	profileIds := make([]int64, len(req.Profiles))
+	for i, profile := range req.Profiles {
+		profileId, _, err := d.cluster.ProfileGet(project, profile)
+		if err == db.ErrNoSuchObject {
+			return response.BadRequest(fmt.Errorf("Profile '%s' doesn't exist", profile))
+		} else if (err != nil) {
+			return response.SmartError(err)
+		}
+		profileIds[i] = profileId
+	}
+
+	err = d.cluster.ImageUpdate(id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties, profileIds)
 	if err != nil {
 		return response.SmartError(err)
 	}
@@ -1664,7 +1676,7 @@ func imagePatch(d *Daemon, r *http.Request) response.Response {
 		info.Properties = properties
 	}
 
-	err = d.cluster.ImageUpdate(id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+	err = d.cluster.ImageUpdate(id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, nil)
 	if err != nil {
 		return response.SmartError(err)
 	}
diff --git a/shared/version/api.go b/shared/version/api.go
index 7a2602b1b5..931606c8f0 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -179,7 +179,7 @@ var APIExtensions = []string{
 	"container_syscall_intercept_mount_fuse",
 	"container_disk_ceph",
 	"virtual-machines",
-	"image_profiles"
+	"image_profiles",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 2e40fc32048b3ad1c6f0eebcbc30539de8201aa8 Mon Sep 17 00:00:00 2001
From: Jack Stenglein <jackstenglein at utexas.edu>
Date: Fri, 29 Nov 2019 15:18:28 -0600
Subject: [PATCH 4/4] lxd/images: Add support for image profiles

Signed-off-by: Rizwan Lubis <rizwan.lubis at gmail.com>
Signed-off-by: Jack Stenglein <jackstenglein at gmail.com>
---
 lxc/init.go      | 21 +++++++++++----------
 lxd/container.go |  7 ++++++-
 lxd/db/images.go | 25 +++++++++++++++++++++++++
 3 files changed, 42 insertions(+), 11 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index 3530ac214f..873cfe10b6 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -219,16 +219,6 @@ func (c *cmdInit) create(conf *config.Config, args []string) (lxd.InstanceServer
 	}
 	req.Config = configMap
 	req.Devices = devicesMap
-
-	if !c.flagNoProfiles && len(profiles) == 0 {
-		if len(stdinData.Profiles) > 0 {
-			req.Profiles = stdinData.Profiles
-		} else {
-			req.Profiles = nil
-		}
-	} else {
-		req.Profiles = profiles
-	}
 	req.Ephemeral = c.flagEphemeral
 
 	var opInfo api.Operation
@@ -274,6 +264,17 @@ func (c *cmdInit) create(conf *config.Config, args []string) (lxd.InstanceServer
 			}
 		}
 
+		// Set up profiles
+		if c.flagNoProfiles {
+			req.Profiles = []string{}
+		} else if len(profiles) > 0 {
+			req.Profiles = profiles
+		} else if len(stdinData.Profiles) > 0 {
+			req.Profiles = stdinData.Profiles
+		} else {
+			req.Profiles = imgInfo.Profiles
+		}
+
 		// Create the instance
 		op, err := d.CreateInstanceFromImage(imgRemote, *imgInfo, req)
 		if err != nil {
diff --git a/lxd/container.go b/lxd/container.go
index 54ab1691d7..9e8b426c89 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -868,7 +868,12 @@ func instanceCreateInternal(s *state.State, args db.InstanceArgs) (instance.Inst
 	}
 
 	if args.Profiles == nil {
-		args.Profiles = []string{"default"}
+		profiles, err := s.Cluster.ImageProfilesGet(args.BaseImage)
+		if err != nil {
+			return nil, err
+		}
+		args.Profiles = profiles
+		logger.Infof("Got profiles: %v", args.Profiles)
 	}
 
 	if args.Config == nil {
diff --git a/lxd/db/images.go b/lxd/db/images.go
index dc9e34878c..eb1bad5b50 100644
--- a/lxd/db/images.go
+++ b/lxd/db/images.go
@@ -737,6 +737,31 @@ func (c *Cluster) ImageAliasUpdate(id int, imageID int, desc string) error {
 	return err
 }
 
+func (c *Cluster) ImageProfilesGet(fingerprint string) ([]string, error) {
+	// Get the profiles
+	q := `
+SELECT profiles.name FROM profiles 
+  JOIN images_profiles ON images_profiles.profile_id = profiles.id
+  JOIN images ON images_profiles.image_id = images.id
+WHERE images.fingerprint = ?
+`
+	var name string
+	inargs := []interface{}{fingerprint}
+	outfmt := []interface{}{name}
+	results, err := queryScan(c.db, q, inargs, outfmt)
+	if err != nil {
+		return nil, err
+	}
+
+	profiles := make([]string, 0)
+	for _, r := range results {
+		name = r[0].(string)
+		profiles = append(profiles, name)
+	}
+
+	return profiles, nil
+}
+
 // ImageLastAccessUpdate updates the last_use_date field of the image with the
 // given fingerprint.
 func (c *Cluster) ImageLastAccessUpdate(fingerprint string, date time.Time) error {


More information about the lxc-devel mailing list