From 357038a7b7ec876cc73896a6e41c9044dd96e4b3 Mon Sep 17 00:00:00 2001 From: Charlie Drage Date: Tue, 6 Aug 2024 13:48:45 -0400 Subject: [PATCH] feat: add chown (linux only) (#695) * feat: add chown (linux only) ### What does this PR do? * Adds chown to the advanced settings page for build * Only applicable to Linux (see: https://github.com/osbuild/bootc-image-builder/issues/576) ### Screenshot / video of UI ### What issues does this PR fix or reference? Closes https://github.com/containers/podman-desktop-extension-bootc/issues/472 ### How to test this PR? 1. Be on Linux 2. Build an image 3. See that it is chown (ex. ls -l /outputdir) Signed-off-by: Charlie Drage * by default fill in uid and gid Signed-off-by: Charlie Drage --------- Signed-off-by: Charlie Drage --- packages/backend/src/api-impl.ts | 6 ++- packages/backend/src/build-disk-image.spec.ts | 25 +++++++++++++ packages/backend/src/build-disk-image.ts | 5 +++ packages/backend/src/machine-utils.ts | 9 +++++ packages/frontend/src/Build.spec.ts | 3 ++ packages/frontend/src/Build.svelte | 37 +++++++++++++++++++ packages/shared/src/BootcAPI.ts | 1 + packages/shared/src/models/bootc.ts | 1 + 8 files changed, 86 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/api-impl.ts b/packages/backend/src/api-impl.ts index 09bf10eb..0c00f1df 100644 --- a/packages/backend/src/api-impl.ts +++ b/packages/backend/src/api-impl.ts @@ -25,7 +25,7 @@ import { History } from './history'; import * as containerUtils from './container-utils'; import { Messages } from '/@shared/src/messages/Messages'; import { telemetryLogger } from './extension'; -import { checkPrereqs, isLinux } from './machine-utils'; +import { checkPrereqs, isLinux, getUidGid } from './machine-utils'; export class BootcApiImpl implements BootcApi { private history: History; @@ -244,6 +244,10 @@ export class BootcApiImpl implements BootcApi { return isLinux(); } + async getUidGid(): Promise { + return getUidGid(); + } + // The API does not allow callbacks through the RPC, so instead // we send "notify" messages to the frontend to trigger a refresh // this method is internal and meant to be used by the API implementation diff --git a/packages/backend/src/build-disk-image.spec.ts b/packages/backend/src/build-disk-image.spec.ts index 98a422c6..d0ad03ad 100644 --- a/packages/backend/src/build-disk-image.spec.ts +++ b/packages/backend/src/build-disk-image.spec.ts @@ -431,3 +431,28 @@ test('test build config json passed in', async () => { expect(options.HostConfig.Binds[2]).toEqual(build.buildConfigFilePath + ':/config.json:ro'); } }); + +test('test chown works when passed into createBuilderImageOptions', async () => { + const name = 'test123-bootc-image-builder'; + const build = { + image: 'test-image', + tag: 'latest', + type: ['raw'], + arch: 'amd64', + folder: '/tmp/foo/bar/qemutest4', + chown: '1000:1000', + } as BootcBuildInfo; + + const options = createBuilderImageOptions(name, build); + + expect(options).toBeDefined(); + expect(options.HostConfig).toBeDefined(); + expect(options.HostConfig?.Binds).toBeDefined(); + if (options.HostConfig?.Binds) { + expect(options.HostConfig.Binds.length).toEqual(2); + expect(options.HostConfig.Binds[0]).toEqual(build.folder + ':/output/'); + expect(options.HostConfig.Binds[1]).toEqual('/var/lib/containers/storage:/var/lib/containers/storage'); + } + expect(options.Cmd).toContain('--chown'); + expect(options.Cmd).toContain(build.chown); +}); diff --git a/packages/backend/src/build-disk-image.ts b/packages/backend/src/build-disk-image.ts index b9b0b8cd..09e7a1a2 100644 --- a/packages/backend/src/build-disk-image.ts +++ b/packages/backend/src/build-disk-image.ts @@ -455,6 +455,11 @@ export function createBuilderImageOptions( } } + // If there is the chown in build, add the --chown flag to the command with the value in chown + if (build.chown) { + cmd.push('--chown', build.chown); + } + return options; } diff --git a/packages/backend/src/machine-utils.ts b/packages/backend/src/machine-utils.ts index b8026a14..d5fe9617 100644 --- a/packages/backend/src/machine-utils.ts +++ b/packages/backend/src/machine-utils.ts @@ -138,3 +138,12 @@ const linux = os.platform() === 'linux'; export function isLinux(): boolean { return linux; } + +// Get the GID and UID of the current user and return in the format gid:uid +// in order for this to work, we must get this information from process.exec +// since there is no native way via node +export async function getUidGid(): Promise { + const { stdout: uidOutput } = await extensionApi.process.exec('id', ['-u']); + const { stdout: gidOutput } = await extensionApi.process.exec('id', ['-g']); + return `${uidOutput.trim()}:${gidOutput.trim()}`; +} diff --git a/packages/frontend/src/Build.spec.ts b/packages/frontend/src/Build.spec.ts index 388dd4f4..d305b6d0 100644 --- a/packages/frontend/src/Build.spec.ts +++ b/packages/frontend/src/Build.spec.ts @@ -728,4 +728,7 @@ test('collapse and uncollapse of advanced options', async () => { // expect build config to be shown const buildConfig2 = screen.queryByRole('label', { name: 'Build config' }); expect(buildConfig2).toBeDefined(); + // Expect chown to be shown + const chown = screen.queryByRole('label', { name: 'Change file owner and group' }); + expect(chown).toBeDefined(); }); diff --git a/packages/frontend/src/Build.svelte b/packages/frontend/src/Build.svelte index a37aa7d8..3201e78a 100644 --- a/packages/frontend/src/Build.svelte +++ b/packages/frontend/src/Build.svelte @@ -34,6 +34,7 @@ let availableArchitectures: string[] = []; // Build options let buildFolder: string; let buildConfigFile: string; +let buildChown: string; let buildType: BuildType[] = []; let buildArch: string | undefined; let buildFilesystem: string = ''; // Default filesystem auto-selected / empty @@ -124,6 +125,17 @@ async function fillArchitectures(historyInfo: BootcBuildInfo[]) { } } +// This will fill the chown function by getting the user and group ID from the OS +// and filling in the information in the chown input field. +async function fillChownOption() { + try { + const gidUid = await bootcClient.getUidGid(); + buildChown = gidUid; + } catch (error) { + console.error('Error getting UID and GID:', error); + } +} + async function validate() { let prereqs = await bootcClient.checkPrereqs(); if (prereqs) { @@ -193,6 +205,7 @@ async function buildBootcImage() { type: buildType, arch: buildArch, filesystem: buildFilesystem, + chown: buildChown, awsAmiName: awsAmiName, awsBucket: awsBucket, awsRegion: awsRegion, @@ -291,6 +304,10 @@ onMount(async () => { await fillBuildOptions(historyInfo); await fillArchitectures(historyInfo); + if (isLinux) { + await fillChownOption(); + } + validate(); }); @@ -689,6 +706,26 @@ export function goToHomePage(): void {

+ + {#if isLinux} +
+ +
+ +
+

+ Linux only. By default the UID and GID of the current user is used. This option allows you to + change the owner and group of the files in the output directory. +

+
+ {/if} +
Upload image to AWS diff --git a/packages/shared/src/BootcAPI.ts b/packages/shared/src/BootcAPI.ts index a873fea0..5096082d 100644 --- a/packages/shared/src/BootcAPI.ts +++ b/packages/shared/src/BootcAPI.ts @@ -36,6 +36,7 @@ export abstract class BootcApi { abstract generateUniqueBuildID(name: string): Promise; abstract openLink(link: string): Promise; abstract isLinux(): Promise; + abstract getUidGid(): Promise; abstract telemetryLogUsage(eventName: string, data?: Record | undefined): Promise; abstract telemetryLogError(eventName: string, data?: Record | undefined): Promise; } diff --git a/packages/shared/src/models/bootc.ts b/packages/shared/src/models/bootc.ts index 23e5800e..c10d4721 100644 --- a/packages/shared/src/models/bootc.ts +++ b/packages/shared/src/models/bootc.ts @@ -26,6 +26,7 @@ export interface BootcBuildInfo { engineId: string; type: BuildType[]; folder: string; + chown?: string; buildConfigFilePath?: string; filesystem?: string; arch?: string;