1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
|
//go:build linux && cgo && !agent
package db
import (
"context"
"fmt"
"maps"
"github.com/lxc/incus/v6/internal/server/db/cluster"
deviceConfig "github.com/lxc/incus/v6/internal/server/device/config"
"github.com/lxc/incus/v6/shared/api"
)
// GetProfileNames returns the names of all profiles in the given project.
func (c *ClusterTx) GetProfileNames(ctx context.Context, project string) ([]string, error) {
q := `
SELECT profiles.name
FROM profiles
JOIN projects ON projects.id = profiles.project_id
WHERE projects.name = ?
`
var result [][]any
enabled, err := cluster.ProjectHasProfiles(context.Background(), c.tx, project)
if err != nil {
return nil, fmt.Errorf("Check if project has profiles: %w", err)
}
if !enabled {
project = "default"
}
inargs := []any{project}
var name string
outfmt := []any{name}
result, err = queryScan(ctx, c, q, inargs, outfmt)
if err != nil {
return nil, err
}
response := []string{}
for _, r := range result {
response = append(response, r[0].(string))
}
return response, nil
}
// GetProfile returns the profile with the given name.
func (c *ClusterTx) GetProfile(ctx context.Context, project, name string) (int64, *api.Profile, error) {
profiles, err := cluster.GetProfilesIfEnabled(ctx, c.tx, project, []string{name})
if err != nil {
return -1, nil, err
}
if len(profiles) != 1 {
return -1, nil, fmt.Errorf("Expected one profile with name %q, got %d profiles", name, len(profiles))
}
profile := profiles[0]
id := int64(profile.ID)
result, err := profile.ToAPI(ctx, c.tx, nil, nil)
if err != nil {
return -1, nil, err
}
return id, result, nil
}
// GetProfiles returns the profiles with the given names in the given project.
func (c *ClusterTx) GetProfiles(ctx context.Context, projectName string, profileNames []string) ([]api.Profile, error) {
profiles := make([]api.Profile, len(profileNames))
dbProfiles, err := cluster.GetProfilesIfEnabled(ctx, c.tx, projectName, profileNames)
if err != nil {
return nil, err
}
// Get all the profile configs.
profileConfigs, err := cluster.GetAllProfileConfigs(ctx, c.Tx())
if err != nil {
return nil, err
}
// Get all the profile devices.
profileDevices, err := cluster.GetAllProfileDevices(ctx, c.Tx())
if err != nil {
return nil, err
}
for i, profile := range dbProfiles {
apiProfile, err := profile.ToAPI(ctx, c.tx, profileConfigs, profileDevices)
if err != nil {
return nil, err
}
profiles[i] = *apiProfile
}
return profiles, nil
}
// GetInstancesWithProfile gets the names of the instance associated with the
// profile with the given name in the given project.
func (c *ClusterTx) GetInstancesWithProfile(ctx context.Context, project, profile string) (map[string][]string, error) {
q := `SELECT instances.name, projects.name FROM instances
JOIN instances_profiles ON instances.id == instances_profiles.instance_id
JOIN projects ON projects.id == instances.project_id
WHERE instances_profiles.profile_id ==
(SELECT profiles.id FROM profiles
JOIN projects ON projects.id == profiles.project_id
WHERE profiles.name=? AND projects.name=?)`
results := map[string][]string{}
var output [][]any
enabled, err := cluster.ProjectHasProfiles(context.Background(), c.tx, project)
if err != nil {
return nil, fmt.Errorf("Check if project has profiles: %w", err)
}
if !enabled {
project = "default"
}
inargs := []any{profile, project}
var name string
outfmt := []any{name, name}
output, err = queryScan(ctx, c, q, inargs, outfmt)
if err != nil {
return nil, err
}
for _, r := range output {
if results[r[1].(string)] == nil {
results[r[1].(string)] = []string{}
}
results[r[1].(string)] = append(results[r[1].(string)], r[0].(string))
}
return results, nil
}
// RemoveUnreferencedProfiles removes unreferenced profiles.
func (c *ClusterTx) RemoveUnreferencedProfiles(ctx context.Context) error {
stmt := `
DELETE FROM profiles_config WHERE profile_id NOT IN (SELECT id FROM profiles);
DELETE FROM profiles_devices WHERE profile_id NOT IN (SELECT id FROM profiles);
DELETE FROM profiles_devices_config WHERE profile_device_id NOT IN (SELECT id FROM profiles_devices);
`
_, err := c.tx.ExecContext(ctx, stmt)
return err
}
// ExpandInstanceConfig expands the given instance config with the config
// values of the given profiles.
func ExpandInstanceConfig(config map[string]string, profiles []api.Profile) map[string]string {
expandedConfig := map[string]string{}
// Apply all the profiles
profileConfigs := make([]map[string]string, len(profiles))
for i, profile := range profiles {
profileConfigs[i] = profile.Config
}
for i := range profileConfigs {
maps.Copy(expandedConfig, profileConfigs[i])
}
// Stick the given config on top
maps.Copy(expandedConfig, config)
return expandedConfig
}
// ExpandInstanceDevices expands the given instance devices with the devices
// defined in the given profiles.
func ExpandInstanceDevices(devices deviceConfig.Devices, profiles []api.Profile) deviceConfig.Devices {
expandedDevices := deviceConfig.Devices{}
// Apply all the profiles
profileDevices := make([]deviceConfig.Devices, len(profiles))
for i, profile := range profiles {
profileDevices[i] = deviceConfig.NewDevices(profile.Devices)
}
for i := range profileDevices {
maps.Copy(expandedDevices, profileDevices[i])
}
// Stick the given devices on top
maps.Copy(expandedDevices, devices)
return expandedDevices
}
|