Skip to content

Commit d673399

Browse files
committed
Fix groups import/export in on-premise environment
add support for both `customizations.groups` (hosted) and `customizations.group` (on-premise) formats. Updated mapper and export logic to handle format conversion between JSON and TOML blueprints.
1 parent 5b4e765 commit d673399

File tree

2 files changed

+76
-20
lines changed

2 files changed

+76
-20
lines changed

playwright/Customizations/Users.spec.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ test('Create a blueprint with Users customization', async ({
9999
).toBeVisible();
100100
await expect(
101101
frame.getByText(
102-
'Each group will automatically be assigned an ID number.',
102+
'Define groups before assigning users to them. Each group will be created on systems using this image. Each group will automatically be assigned an ID number.',
103103
),
104104
).toBeVisible();
105105

@@ -117,9 +117,18 @@ test('Create a blueprint with Users customization', async ({
117117
const gidInputs = frame.getByPlaceholder('Auto-generated');
118118
await expect(gidInputs.nth(0)).toHaveValue(/^\d+$/);
119119
await expect(gidInputs.nth(1)).toHaveValue(/^\d+$/);
120+
// Verify GID inputs are disabled (read-only)
121+
await expect(gidInputs.nth(0)).toBeDisabled();
122+
await expect(gidInputs.nth(1)).toBeDisabled();
120123
});
121124

122125
await test.step('Test group validation errors', async () => {
126+
// Get Groups section container for scoped assertions
127+
const groupsSection = frame
128+
.getByRole('heading', { name: 'Groups' })
129+
.first()
130+
.locator('..');
131+
123132
const groupNameInputs = frame.getByPlaceholder('Set group name');
124133
const addGroupButton = frame.getByRole('button', { name: 'Add group' });
125134
await expect(addGroupButton).toBeVisible();
@@ -130,28 +139,28 @@ test('Create a blueprint with Users customization', async ({
130139
await expect(newGroupInput1).toBeVisible();
131140
await expect(newGroupInput1).toBeEnabled();
132141

133-
await expect(frame.getByText('Invalid group name')).toBeHidden();
142+
await expect(groupsSection.getByText('Invalid group name')).toBeHidden();
134143
await newGroupInput1.fill('@invalid');
135144
await expect(newGroupInput1).toHaveValue('@invalid');
136-
await expect(frame.getByText('Invalid group name')).toBeVisible();
145+
await expect(groupsSection.getByText('Invalid group name')).toBeVisible();
137146

138147
const addGroupButtons = frame.getByRole('button', { name: 'Add group' });
139148
await expect(addGroupButtons.last()).toBeDisabled();
140149

141150
await newGroupInput1.clear();
142-
await expect(frame.getByText('Invalid group name')).toBeHidden();
151+
await expect(groupsSection.getByText('Invalid group name')).toBeHidden();
143152
await expect(newGroupInput1).toHaveValue('');
144153
await newGroupInput1.fill('develop');
145154
await expect(newGroupInput1).toHaveValue('develop');
146-
await expect(frame.getByText('Invalid group name')).toBeHidden();
155+
await expect(groupsSection.getByText('Invalid group name')).toBeHidden();
147156
await expect(addGroupButtons.last()).toBeEnabled();
148157

149158
await newGroupInput1.clear();
150159
await expect(newGroupInput1).toHaveValue('');
151160
await newGroupInput1.fill('admins');
152161
await expect(newGroupInput1).toHaveValue('admins');
153162
await expect(
154-
frame.getByText('Group name already exists').first(),
163+
groupsSection.getByText('Group name already exists').first(),
155164
).toBeVisible();
156165
await expect(addGroupButtons.last()).toBeDisabled();
157166

@@ -228,11 +237,14 @@ test('Create a blueprint with Users customization', async ({
228237
.first();
229238
await expect(warningMessage).toBeVisible();
230239

231-
const closeButton = frame
240+
// Remove customgroup
241+
const customgroupCloseButton = frame
232242
.getByRole('button', { name: 'Close customgroup' })
233243
.first();
234-
await expect(closeButton).toBeVisible();
235-
await closeButton.click();
244+
await expect(customgroupCloseButton).toBeVisible();
245+
await customgroupCloseButton.click();
246+
247+
// Warning should be hidden now because 'wheel' is a system group and not considered undefined
236248
await expect(warningMessage).toBeHidden();
237249
});
238250

src/Components/Blueprints/helpers/onPremToHostedBlueprintMapper.tsx

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,16 @@ export type CustomizationsOnPrem = {
7777
hostname?: string;
7878
kernel?: Kernel;
7979
user?: UserOnPrem[];
80+
/**
81+
* User groups with GIDs (plural form, used in Hosted environment).
82+
* Hosted blueprints use this field in JSON format.
83+
*/
8084
groups?: GroupOnPrem[];
85+
/**
86+
* User groups with GIDs (singular form, used in On-Prem environment).
87+
* On-Prem blueprints use this field in TOML format: [[customizations.group]]
88+
*/
89+
group?: GroupOnPrem[];
8190
timezone?: Timezone;
8291
locale?: Locale;
8392
firewall?: FirewallCustomization;
@@ -123,9 +132,11 @@ export const mapOnPremToHosted = async (
123132
blueprint.packages !== undefined
124133
? blueprint.packages.map((p) => p.name)
125134
: undefined;
126-
const groups =
127-
blueprint.customizations?.groups !== undefined
128-
? blueprint.customizations.groups.map((p) => `@${p.name}`)
135+
// Note: blueprint.groups refers to package groups (e.g., @development-tools),
136+
// not user groups. User groups are in customizations.groups (see below).
137+
const packageGroups =
138+
blueprint.groups !== undefined
139+
? blueprint.groups.map((p) => `@${p.name}`)
129140
: undefined;
130141
const distro = process.env.IS_ON_PREMISE
131142
? await getHostDistro()
@@ -144,14 +155,19 @@ export const mapOnPremToHosted = async (
144155
}),
145156
),
146157
packages:
147-
packages !== undefined || groups !== undefined
148-
? [...(packages ? packages : []), ...(groups ? groups : [])]
158+
packages !== undefined || packageGroups !== undefined
159+
? [
160+
...(packages ? packages : []),
161+
...(packageGroups ? packageGroups : []),
162+
]
149163
: undefined,
150164
users:
151165
users !== undefined || user_keys !== undefined
152166
? [...(users ? users : []), ...(user_keys ? user_keys : [])]
153167
: undefined,
154-
groups: blueprint.customizations?.groups,
168+
// Support both 'groups' (hosted/current) and 'group' (on-prem/legacy) formats
169+
groups:
170+
blueprint.customizations?.groups || blueprint.customizations?.group,
155171
filesystem: blueprint.customizations?.filesystem?.map(
156172
({ minsize, size, ...fs }) => ({
157173
min_size: minsize || size,
@@ -207,12 +223,36 @@ export const mapHostedToOnPrem = (
207223
};
208224

209225
if (blueprint.customizations.packages) {
210-
result.packages = blueprint.customizations.packages.map((pkg) => {
211-
return {
212-
name: pkg,
213-
version: '*',
214-
};
226+
// Separate regular packages from package groups (those starting with @)
227+
const regularPackages: string[] = [];
228+
const packageGroups: string[] = [];
229+
230+
blueprint.customizations.packages.forEach((pkg) => {
231+
if (pkg.startsWith('@')) {
232+
// Package group (e.g., @development-tools) - remove @ prefix and add to groups
233+
packageGroups.push(pkg.substring(1));
234+
} else {
235+
// Regular package
236+
regularPackages.push(pkg);
237+
}
215238
});
239+
240+
// Map regular packages to packages array
241+
if (regularPackages.length > 0) {
242+
result.packages = regularPackages.map((pkg) => {
243+
return {
244+
name: pkg,
245+
version: '*',
246+
};
247+
});
248+
}
249+
250+
// Map package groups to blueprint.groups array
251+
if (packageGroups.length > 0) {
252+
result.groups = packageGroups.map((groupName) => ({
253+
name: groupName,
254+
}));
255+
}
216256
}
217257

218258
if (blueprint.customizations.containers) {
@@ -242,6 +282,10 @@ export const mapHostedToOnPrem = (
242282
result.customizations!.disk = blueprint.customizations.disk;
243283
}
244284

285+
if (blueprint.customizations.groups) {
286+
result.customizations!.group = blueprint.customizations.groups;
287+
}
288+
245289
if (blueprint.customizations.users) {
246290
result.customizations!.user = blueprint.customizations.users.map((u) => {
247291
return {

0 commit comments

Comments
 (0)