-
Notifications
You must be signed in to change notification settings - Fork 64
store/cockpit: use ibcli for building image (HMS-9389) #3636
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
7df1956
03e901c
bdf7414
97ef36d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| FROM ubuntu:latest AS builder | ||
| RUN apt update | ||
| RUN apt install -y git golang libgpgme-dev libbtrfs-dev libdevmapper-dev podman | ||
| RUN git clone https://github.com/osbuild/image-builder-cli.git ibcli | ||
| WORKDIR ibcli | ||
| RUN ls -al | ||
| RUN go mod tidy && go mod vendor | ||
| RUN go build -o /opt ./cmd/image-builder/ | ||
|
|
||
| FROM mcr.microsoft.com/playwright:v1.51.1-noble | ||
| RUN apt update | ||
| # Needed for running image-builder | ||
| RUN apt install -y libgpgme-dev libbtrfs-dev libdevmapper-dev | ||
| COPY --from=builder /opt /usr/bin |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,8 @@ sudo useradd admin -p "$(openssl passwd foobar)" | |
| sudo usermod -aG wheel admin | ||
| echo "admin ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/admin-nopasswd" | ||
|
|
||
| sudo podman build --tag playwright -f $(pwd)/schutzbot/Containerfile-Playwright . | ||
|
|
||
| function upload_artifacts { | ||
| if [ -n "${TMT_TEST_DATA:-}" ]; then | ||
| mv playwright-report "$TMT_TEST_DATA"/playwright-report | ||
|
|
@@ -88,5 +90,5 @@ sudo podman run \ | |
| --privileged \ | ||
| --rm \ | ||
| --init \ | ||
| mcr.microsoft.com/playwright:v1.51.1-noble \ | ||
| localhost/playwright \ | ||
| /bin/sh -c "cd tests && npx -y [email protected] test --workers=${PW_WORKERS}" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import path from 'path'; | |
| import TOML, { Section } from '@ltd/j-toml'; | ||
| import cockpit from 'cockpit'; | ||
| import { fsinfo } from 'cockpit/fsinfo'; | ||
| import { LongRunningProcess } from 'long-running-process'; | ||
| import { v4 as uuidv4 } from 'uuid'; | ||
|
|
||
| // We have to work around RTK query here, since it doesn't like splitting | ||
|
|
@@ -19,17 +20,21 @@ import { v4 as uuidv4 } from 'uuid'; | |
| // bit so that the `cockpitApi` doesn't become a monolith. | ||
| import { contentSourcesApi } from './contentSourcesApi'; | ||
| import { | ||
| ComposeStatus, | ||
| composeStatus, | ||
| datastreamDistroLookup, | ||
| getBlueprintsPath, | ||
| getCloudConfigs, | ||
| mapToOnpremRequest, | ||
| paginate, | ||
| readComposes, | ||
| updateComposeStatus, | ||
| } from './helpers'; | ||
| import type { | ||
| CockpitCreateBlueprintApiArg, | ||
| CockpitCreateBlueprintRequest, | ||
| CockpitUpdateBlueprintApiArg, | ||
| GetCockpitComposeStatusApiResponse, | ||
| ImageStatus, | ||
| UpdateWorkerConfigApiArg, | ||
| WorkerConfigFile, | ||
| WorkerConfigResponse, | ||
|
|
@@ -39,6 +44,7 @@ import { | |
| mapHostedToOnPrem, | ||
| mapOnPremToHosted, | ||
| } from '../../Components/Blueprints/helpers/onPremToHostedBlueprintMapper'; | ||
| import { ARTIFACTS_DIR } from '../../constants'; | ||
| import { | ||
| BlueprintItem, | ||
| ComposeBlueprintApiArg, | ||
|
|
@@ -61,7 +67,6 @@ import { | |
| GetComposesApiArg, | ||
| GetComposesApiResponse, | ||
| GetComposeStatusApiArg, | ||
| GetComposeStatusApiResponse, | ||
| GetOscapCustomizationsApiArg, | ||
| GetOscapCustomizationsApiResponse, | ||
| GetOscapProfilesApiArg, | ||
|
|
@@ -359,7 +364,7 @@ export const cockpitApi = contentSourcesApi.injectEndpoints({ | |
| ComposeBlueprintApiResponse, | ||
| ComposeBlueprintApiArg | ||
| >({ | ||
| queryFn: async ({ id: filename }, _, __, baseQuery) => { | ||
| queryFn: async ({ id: filename }) => { | ||
| try { | ||
| const blueprintsDir = await getBlueprintsPath(); | ||
| const file = cockpit.file( | ||
|
|
@@ -386,29 +391,63 @@ export const cockpitApi = contentSourcesApi.injectEndpoints({ | |
| image_requests: [ir], | ||
| }; | ||
|
|
||
| const composeResp = await baseQuery({ | ||
| url: '/compose', | ||
| method: 'POST', | ||
| body: JSON.stringify( | ||
| // since this is the request that gets sent to the cloudapi | ||
| // backend, we need to modify it slightly | ||
| mapToOnpremRequest( | ||
| const uuid = uuidv4(); | ||
| const composeDir = path.join(blueprintsDir, filename, uuid); | ||
| await cockpit.spawn(['mkdir', '-p', composeDir], {}); | ||
|
|
||
| const ibBpPath = path.join(composeDir, 'bp.json'); | ||
| await cockpit | ||
| .file(ibBpPath) | ||
| .replace( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh why rewrite the blueprint here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need it so we can pass it to |
||
| JSON.stringify( | ||
| mapHostedToOnPrem(blueprint as CreateBlueprintRequest), | ||
| crcComposeRequest.distribution, | ||
| [ir], | ||
| null, | ||
| 2, | ||
| ), | ||
| null, | ||
| 2, | ||
| ), | ||
| headers: { | ||
| 'content-type': 'application/json', | ||
| }, | ||
| }); | ||
| ); | ||
|
|
||
| // save the blueprint request early, since any errors | ||
| // in this function cause pretty big headaches with | ||
| // the images table | ||
| await cockpit | ||
| .file(path.join(blueprintsDir, filename, composeResp.data?.id)) | ||
| .file(path.join(composeDir, 'request.json')) | ||
| .replace(JSON.stringify(crcComposeRequest, null, 2)); | ||
| composes.push({ id: composeResp.data?.id }); | ||
|
|
||
| const user = await cockpit.user(); | ||
| const cmd = [ | ||
| // the image build fails if we don't set | ||
| // this for some reason | ||
| `HOME=${user.home}`, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be because the systemd process is running as root? Not sure how we could drop it though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I put a note there and created a feature request for cockpit, basically the env variables aren't being set for the systemd service. The solution is to either import the |
||
| '/usr/bin/image-builder', | ||
| 'build', | ||
| '--with-buildlog', | ||
| '--blueprint', | ||
| ibBpPath, | ||
| '--output-dir', | ||
| path.join(ARTIFACTS_DIR, uuid), | ||
| '--output-name', | ||
| uuid, | ||
| '--distro', | ||
| crcComposeRequest.distribution, | ||
| ir.image_type, | ||
| ]; | ||
|
|
||
| const process = new LongRunningProcess( | ||
| `cockpit-image-builder-${uuid}.service`, | ||
| updateComposeStatus(composeDir), | ||
| ); | ||
|
|
||
| // this is a workaround because the process | ||
| // can't be started when in `init` state | ||
| process.state = 'stopped'; | ||
|
|
||
| process.run(['bash', '-ec', cmd.join(' ')]).catch(async () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it seems cockpit works with arrays of strings, so would It seems this could then also use the same trick that https://github.com/cockpit-project/cockpit/blob/main/examples/long-running-process/index.js#L20 is using, i.e. just monitor the jounal to get the output of ibcli, with that we could add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ya I didn't like that either and adding HOME= is not ideal. But I ran into two issues here:
But those are both valid points and I will dig into it deeper and try figure out why that wasn't working. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the json progress that seems nice and we could probably implement that in the getComposeStatus endpoint There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kingsleyzissou before you spend/waste too much time in debugging this please leave and maybe we can have a joint session where you can help me to reproduce so that I can dig into it a bit (if you want). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry I actually missed this comment. I came back to it today in preparation for Friday's call. I filed a feature request in cockpit: |
||
| await cockpit | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we get Or could we attach There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah that's what we're doing, there is a callback that is handling this too and should be attached to the systemd service afaict. I'll have to double check this. |
||
| .file(path.join(composeDir, 'result')) | ||
| .replace(ComposeStatus.FAILURE); | ||
| }); | ||
|
|
||
| composes.push({ id: uuid }); | ||
| } | ||
|
|
||
| return { | ||
|
|
@@ -452,37 +491,47 @@ export const cockpitApi = contentSourcesApi.injectEndpoints({ | |
| }, | ||
| }), | ||
| getComposeStatus: builder.query< | ||
| GetComposeStatusApiResponse, | ||
| GetCockpitComposeStatusApiResponse, | ||
| GetComposeStatusApiArg | ||
| >({ | ||
| queryFn: async (queryArg, _, __, baseQuery) => { | ||
| queryFn: async (queryArg) => { | ||
| try { | ||
| const resp = await baseQuery({ | ||
| url: `/composes/${queryArg.composeId}`, | ||
| method: 'GET', | ||
| }); | ||
| const blueprintsDir = await getBlueprintsPath(); | ||
| const info = await fsinfo(blueprintsDir, ['entries'], { | ||
| superuser: 'try', | ||
| }); | ||
| const entries = Object.entries(info?.entries || {}); | ||
| for (const bpEntry of entries) { | ||
| for await (const bpEntry of entries) { | ||
| const bpComposes = await readComposes(bpEntry[0]); | ||
| if (!bpComposes.some((c) => c.id === queryArg.composeId)) { | ||
| continue; | ||
| } | ||
|
|
||
| const request = await cockpit | ||
| .file(path.join(blueprintsDir, bpEntry[0], queryArg.composeId)) | ||
| .file( | ||
| path.join( | ||
| blueprintsDir, | ||
| bpEntry[0], | ||
| queryArg.composeId, | ||
| 'request.json', | ||
| ), | ||
| ) | ||
| .read(); | ||
|
|
||
| const status = await composeStatus( | ||
| queryArg.composeId, | ||
| path.join(blueprintsDir, bpEntry[0], queryArg.composeId), | ||
| ); | ||
|
|
||
| return { | ||
| data: { | ||
| image_status: resp.data?.image_status, | ||
| image_status: status as ImageStatus, | ||
| request: JSON.parse(request), | ||
| }, | ||
| }; | ||
| } | ||
| return { | ||
| data: { | ||
| image_status: '', | ||
| request: {}, | ||
| }, | ||
| }; | ||
|
|
||
| throw new Error('Compose not found'); | ||
| } catch (error) { | ||
| return { error }; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.