Skip to content

Commit 8f8a567

Browse files
committed
fix: enable selinux relabeling for podman compatibility
1 parent 77bbfe7 commit 8f8a567

4 files changed

Lines changed: 129 additions & 3 deletions

File tree

packages/create-plugin/src/codemods/migrations/migrations.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ export default [
7171
'Fix ts-node compatibility with the latest @grafana/tsconfig: outdated module/moduleResolution/target overrides break TypeScript 5/6 builds, replaced with nodenext/nodenext/es2022.',
7272
scriptPath: import.meta.resolve('./scripts/010-ts-node-nodenext.js'),
7373
},
74+
{
75+
name: '012-enable-compose-selinux-relabel',
76+
version: '7.4.1',
77+
description: 'Enable SELinux bindmount relabeling allowing the use of rootless podman for plugin development',
78+
scriptPath: import.meta.resolve('./scripts/012-enable-compose-selinux-relabel.js'),
79+
},
7480
// Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
7581
// for those written before the switch to updates as migrations.
7682
] satisfies Migration[];
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { Context } from '../../context.js';
3+
import migrate from './012-enable-compose-selinux-relabel.js';
4+
import { parse, stringify } from 'yaml';
5+
6+
describe('012-enable-compose-selinux-relabel', () => {
7+
it('should not modify anything if base compose file does not exist', async () => {
8+
const context = new Context('/virtual');
9+
context.addFile(
10+
'./docker-compose.yaml',
11+
stringify({
12+
services: {
13+
grafana: {
14+
volumes: ['/foo:/bar'],
15+
},
16+
},
17+
})
18+
);
19+
const initialChanges = context.listChanges();
20+
await migrate(context);
21+
expect(context.listChanges()).toEqual(initialChanges);
22+
});
23+
24+
it('should add :Z to all bindmounts', async () => {
25+
const context = new Context('/virtual');
26+
context.addFile(
27+
'./.config/docker-compose-base.yaml',
28+
stringify({
29+
services: {
30+
grafana: {
31+
volumes: ['../provisioning:/etc/grafana/provisioning', '..:/root/test-plugin'],
32+
},
33+
},
34+
})
35+
);
36+
await migrate(context);
37+
38+
const result = parse(context.getFile('./.config/docker-compose-base.yaml') || '');
39+
expect(result.services.grafana.volumes).toEqual([
40+
'../provisioning:/etc/grafana/provisioning:Z',
41+
'..:/root/test-plugin:Z',
42+
]);
43+
});
44+
45+
it('should modify existing bindmount opts', async () => {
46+
const context = new Context('/virtual');
47+
context.addFile(
48+
'./.config/docker-compose-base.yaml',
49+
stringify({
50+
services: {
51+
grafana: {
52+
volumes: ['../provisioning:/etc/grafana/provisioning:ro', '..:/root/test-plugin:z'],
53+
},
54+
},
55+
})
56+
);
57+
await migrate(context);
58+
59+
const result = parse(context.getFile('./.config/docker-compose-base.yaml') || '');
60+
expect(result.services.grafana.volumes).toEqual([
61+
'../provisioning:/etc/grafana/provisioning:roZ',
62+
'..:/root/test-plugin:z',
63+
]);
64+
});
65+
66+
it('should be idempotent', async () => {
67+
const context = new Context('/virtual');
68+
context.addFile(
69+
'./.config/docker-compose-base.yaml',
70+
stringify({
71+
services: {
72+
grafana: {
73+
volumes: ['../provisioning:/etc/grafana/provisioning:Z', '..:/root/test-plugin:Z'],
74+
},
75+
},
76+
})
77+
);
78+
await expect(migrate).toBeIdempotent(context);
79+
});
80+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { type Context } from '../../context.js';
2+
import { parseDocument, stringify, isSeq, isScalar } from 'yaml';
3+
4+
export default async function migrate(context: Context) {
5+
const baseComposeContent = context.getFile('./.config/docker-compose-base.yaml');
6+
7+
if (!baseComposeContent) {
8+
return context;
9+
}
10+
11+
const baseComposeData = parseDocument(baseComposeContent);
12+
13+
const mounts = baseComposeData.getIn(['services', 'grafana', 'volumes']);
14+
if (!isSeq(mounts)) {
15+
return context;
16+
}
17+
for (const m of mounts.items) {
18+
if (!isScalar(m) || typeof m.value !== 'string') {
19+
continue;
20+
}
21+
22+
const parts = m.value.split(':');
23+
if (parts.length < 3) {
24+
m.value += ':Z';
25+
continue;
26+
}
27+
28+
const opts = parts[parts.length - 1];
29+
if (!opts.match(/[zZ]/)) {
30+
m.value = [...parts.slice(0, -1), opts + 'Z'].join(':');
31+
}
32+
}
33+
34+
context.updateFile(
35+
'./.config/docker-compose-base.yaml',
36+
stringify(baseComposeData, { lineWidth: 120, singleQuote: true })
37+
);
38+
39+
return context;
40+
}

packages/create-plugin/templates/common/.config/docker-compose-base.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ services:
2121
- SYS_PTRACE
2222
{{/if}}
2323
volumes:
24-
- ../dist:/var/lib/grafana/plugins/{{ pluginId }}
25-
- ../provisioning:/etc/grafana/provisioning
26-
- ..:/root/{{ pluginId }}
24+
- ../dist:/var/lib/grafana/plugins/{{ pluginId }}:Z
25+
- ../provisioning:/etc/grafana/provisioning:Z
26+
- ..:/root/{{ pluginId }}:Z
2727

2828
environment:
2929
NODE_ENV: development

0 commit comments

Comments
 (0)