From b85d9240c02b512c18972dc3141d07eced78c095 Mon Sep 17 00:00:00 2001 From: Gianluca Zuccarelli Date: Tue, 19 Nov 2024 19:20:44 +0000 Subject: [PATCH] store/cockpitApi: scan for blueprint files Add an initial commit to scan a preset directory for user blueprint files. This makes use of the cockpit files api. --- src/store/cockpitApi.ts | 65 ++++++++++++++++++++++++++++++++++------- src/test/setup.ts | 22 ++++++++++++++ 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/store/cockpitApi.ts b/src/store/cockpitApi.ts index 6691c54ebc..3578b4aa57 100644 --- a/src/store/cockpitApi.ts +++ b/src/store/cockpitApi.ts @@ -1,3 +1,4 @@ +import TOML from '@ltd/j-toml'; import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { @@ -5,8 +6,16 @@ import { GetArchitecturesApiArg, GetBlueprintsApiArg, GetBlueprintsApiResponse, + BlueprintItem, } from './imageBuilderApi'; +// we could create an alias for this, something like +// import cockpit from 'cockpit', but this feels like +// a bit of magic and might make the code harder to +// maintain. +import cockpit from '../../pkg/lib/cockpit'; +import { fsinfo } from '../../pkg/lib/cockpit/fsinfo'; + const emptyCockpitApi = createApi({ reducerPath: 'cockpitApi', baseQuery: fetchBaseQuery({ baseUrl: '/api' }), @@ -28,22 +37,58 @@ export const cockpitApi = emptyCockpitApi.injectEndpoints({ GetBlueprintsApiResponse, GetBlueprintsApiArg >({ - queryFn: () => { - // TODO: Add cockpit file api support for reading in blueprints. - // For now we're just hardcoding a dummy response - // so we can render an empty table. - return new Promise((resolve) => { - resolve({ + queryFn: async () => { + try { + if (!cockpit) { + throw new Error('Cockpit API is not available'); + } + + const user = await cockpit.user(); + + // we will use the user's `.local` directory + // to save blueprints used for on-prem + // TODO: remove the hardcode + const path = `${user.home}/.local/share/cockpit/image-builder-frontend/blueprints`; + + // we probably don't need any more information other + // than the entries from the directory + const info = await fsinfo(path, ['entries'], { + superuser: 'try', + }); + + const entries = Object.entries(info?.entries || {}); + const blueprints: BlueprintItem[] = await Promise.all( + entries.map(async ([filename]) => { + const file = cockpit.file(`${path}/${filename}`); + + const contents = await file.read(); + const parsed = TOML.parse(contents); + file.close(); + + return { + name: parsed.name as string, + id: parsed.name as string, // TODO: duplicate name case + version: parsed.version as number, + description: parsed.description as string, + last_modified_at: Date.now().toString(), + }; + }) + ); + + return { data: { - meta: { count: 0 }, + meta: { count: blueprints.length }, links: { + // TODO: figure out the pagination first: '', last: '', }, - data: [], + data: blueprints, }, - }); - }); + }; + } catch (error) { + return { error: error.message }; + } }, }), }; diff --git a/src/test/setup.ts b/src/test/setup.ts index b3da851815..4e74e28e8d 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -79,6 +79,28 @@ vi.mock('@unleash/proxy-client-react', () => ({ }), })); +vi.mock(import('../../pkg/lib/cockpit'), async () => { + return { + user: () => { + new Promise((resolve) => { + resolve({ + home: '/home/osbuild', + }); + }); + }, + }; +}); + +vi.mock(import('../../pkg/lib/cockpit/fsinfo'), async () => { + return { + fsinfo: () => { + new Promise((resolve) => { + resolve({}); + }); + }, + }; +}); + // Remove DOM dump from the testing-library output configure({ getElementError: (message: string) => {