File: profiles.go

package info (click to toggle)
incus 6.0.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 24,392 kB
  • sloc: sh: 16,313; ansic: 3,121; python: 457; makefile: 337; ruby: 51; sql: 50; lisp: 6
file content (202 lines) | stat: -rw-r--r-- 5,479 bytes parent folder | download | duplicates (3)
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
}