-
Notifications
You must be signed in to change notification settings - Fork 53
feat(dashmate): configure docker build args via config #3764
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: v3.1-dev
Are you sure you want to change the base?
Changes from all commits
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 | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,6 +32,15 @@ export default { | |||||||||||||||||||||||||
| target: { | ||||||||||||||||||||||||||
| type: ['string', 'null'], | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| // Extra build args forwarded to `docker compose build` for this | ||||||||||||||||||||||||||
| // image. Each key/value pair becomes a `build.args` entry rendered | ||||||||||||||||||||||||||
| // into the per-config `dynamic-compose.yml` and picked up by | ||||||||||||||||||||||||||
| // compose at build time. Open-ended — image-specific keys live | ||||||||||||||||||||||||||
| // here (`CARGO_BUILD_PROFILE`, `SDK_TEST_DATA`, …). | ||||||||||||||||||||||||||
| buildArgs: { | ||||||||||||||||||||||||||
| type: 'object', | ||||||||||||||||||||||||||
| additionalProperties: { type: 'string' }, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
|
Comment on lines
+40
to
+43
Contributor
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. Constrain At Line 49, Suggested schema hardening buildArgs: {
type: 'object',
+ propertyNames: {
+ type: 'string',
+ pattern: '^[A-Z_][A-Z0-9_]*$',
+ },
additionalProperties: { type: 'string' },
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| required: ['enabled', 'context', 'dockerFile', 'target'], | ||||||||||||||||||||||||||
| additionalProperties: false, | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,13 +13,23 @@ services: | |
| {{?}} | ||
|
|
||
| {{ driveLogs = Object.entries(it.platform.drive.abci.logs).filter(([, settings]) => settings.destination !== 'stderr' && settings.destination !== 'stdout'); }} | ||
| {{? driveLogs.length > 0 }} | ||
| {{ driveBuildArgs = Object.entries((it.platform.drive.abci.docker.build && it.platform.drive.abci.docker.build.buildArgs) || {}); }} | ||
| {{? driveLogs.length > 0 || driveBuildArgs.length > 0 }} | ||
| drive_abci: | ||
| {{? driveLogs.length > 0 }} | ||
| volumes: | ||
| {{~ driveLogs :logger }} | ||
| {{ [name, settings] = logger; }} | ||
| - {{=settings.destination}}:/var/log/dash/drive/{{=name}}/{{=settings.destination.split('/').reverse()[0]}} | ||
| {{~}} | ||
| {{?}} | ||
| {{? driveBuildArgs.length > 0 }} | ||
| build: | ||
| args: | ||
| {{~ driveBuildArgs :pair }} | ||
| {{=pair[0]}}: "{{=pair[1]}}" | ||
|
Collaborator
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. 🟡 Suggestion: buildArgs values are interpolated into YAML without escaping Lines 30 and 58 render each pair as source: ['claude', 'codex'] |
||
| {{~}} | ||
| {{?}} | ||
| {{?}} | ||
|
|
||
| {{? it.platform.drive.tenderdash.log.path !== null }} | ||
|
|
@@ -29,6 +39,7 @@ services: | |
| {{?}} | ||
|
|
||
| {{? it.platform.dapi && it.platform.dapi.rsDapi }} | ||
| {{ rsDapiBuildArgs = Object.entries((it.platform.dapi.rsDapi.docker.build && it.platform.dapi.rsDapi.docker.build.buildArgs) || {}); }} | ||
| rs_dapi: | ||
| expose: | ||
| - 3009 | ||
|
|
@@ -40,6 +51,13 @@ services: | |
| ports: | ||
| - {{=it.platform.dapi.rsDapi.metrics.host}}:{{=it.platform.dapi.rsDapi.metrics.port}}:{{=it.platform.dapi.rsDapi.metrics.port}} | ||
| {{?}} | ||
| {{? rsDapiBuildArgs.length > 0 }} | ||
| build: | ||
| args: | ||
| {{~ rsDapiBuildArgs :pair }} | ||
| {{=pair[0]}}: "{{=pair[1]}}" | ||
| {{~}} | ||
| {{?}} | ||
| {{?}} | ||
|
|
||
| {{ gatewayLogs = it.platform.gateway.log.accessLogs.filter((l) => l.type === 'file'); }} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| import { expect } from 'chai'; | ||
| import Config from '../../../src/config/Config.js'; | ||
|
|
||
| describe('Config', () => { | ||
| describe('.isSchemaPathAllowed', () => { | ||
| // The bug that triggered this method: `dashmate config set | ||
| // platform.drive.abci.docker.build.buildArgs.SDK_TEST_DATA true` | ||
| // was failing because the old `config.get(path)` pre-check rejected | ||
| // any path whose value isn't already stored — including new keys | ||
| // inside map-shaped properties whose schema legally accepts them | ||
| // via `additionalProperties: <schema>`. Each test pins one slice | ||
| // of the schema-walk so a regression surfaces fast. | ||
|
|
||
| describe('typed properties', () => { | ||
| it('accepts a deeply-nested path that traverses only `properties`', () => { | ||
| expect( | ||
| Config.isSchemaPathAllowed('platform.drive.abci.docker.build.enabled'), | ||
| ).to.be.true(); | ||
| }); | ||
|
|
||
| it('accepts a top-level property', () => { | ||
| expect(Config.isSchemaPathAllowed('network')).to.be.true(); | ||
| }); | ||
|
|
||
| it('rejects a top-level typo', () => { | ||
| // `platfom` (typo) is not in top-level `properties` and the schema | ||
| // has `additionalProperties: false`, so it must not be allowed. | ||
| expect( | ||
| Config.isSchemaPathAllowed('platfom.drive.abci.docker.build.enabled'), | ||
| ).to.be.false(); | ||
| }); | ||
|
|
||
| it('rejects a typo in the middle of the path', () => { | ||
| expect( | ||
| Config.isSchemaPathAllowed('platform.drive.abc.docker.build.enabled'), | ||
| ).to.be.false(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('map-shaped properties (additionalProperties: <schema>)', () => { | ||
| it('accepts a new key inside a value-keyed map', () => { | ||
| // The original failure. `buildArgs` is defined as | ||
| // `{ type: 'object', additionalProperties: { type: 'string' } }` | ||
| // so any key with a string value is schema-legal even if not yet set. | ||
| expect( | ||
| Config.isSchemaPathAllowed( | ||
| 'platform.drive.abci.docker.build.buildArgs.SDK_TEST_DATA', | ||
| ), | ||
| ).to.be.true(); | ||
| }); | ||
|
|
||
| it('accepts an arbitrary key inside the map (the whole point)', () => { | ||
| expect( | ||
| Config.isSchemaPathAllowed( | ||
| 'platform.drive.abci.docker.build.buildArgs.ANY_KEY_AT_ALL', | ||
| ), | ||
| ).to.be.true(); | ||
| }); | ||
|
|
||
| it('accepts the map property itself', () => { | ||
| expect( | ||
| Config.isSchemaPathAllowed('platform.drive.abci.docker.build.buildArgs'), | ||
| ).to.be.true(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('$ref traversal', () => { | ||
| it('descends through a $ref to `#/definitions/dockerBuild`', () => { | ||
| // `platform.drive.abci.docker.build` resolves via $ref to the shared | ||
| // `dockerBuild` definition — buildArgs is defined there. | ||
| expect( | ||
| Config.isSchemaPathAllowed( | ||
| 'platform.drive.abci.docker.build.buildArgs.X', | ||
| ), | ||
| ).to.be.true(); | ||
| }); | ||
|
|
||
| it('descends through a $ref for a sibling Rust build (rs-dapi)', () => { | ||
| expect( | ||
| Config.isSchemaPathAllowed( | ||
| 'platform.dapi.rsDapi.docker.build.buildArgs.X', | ||
| ), | ||
| ).to.be.true(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('edge cases', () => { | ||
| it('rejects an empty path', () => { | ||
| expect(Config.isSchemaPathAllowed('')).to.be.false(); | ||
| }); | ||
|
|
||
| it('rejects a non-string path', () => { | ||
| expect(Config.isSchemaPathAllowed(null)).to.be.false(); | ||
| expect(Config.isSchemaPathAllowed(undefined)).to.be.false(); | ||
| expect(Config.isSchemaPathAllowed(42)).to.be.false(); | ||
| }); | ||
|
|
||
| it('rejects descending past a leaf primitive', () => { | ||
| // `network` is a string at top level; you cannot index further. | ||
| expect(Config.isSchemaPathAllowed('network.something')).to.be.false(); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('regression: paths the buggy pre-check rejected are now permitted', () => { | ||
| // Before the fix, `dashmate config set` did a value-existence pre-check | ||
| // (`config.get(path)`) that threw `InvalidOptionPathError` for any path | ||
| // whose value wasn't already stored. That blocked legal sets under | ||
| // map-shaped properties (`additionalProperties: <schema>`). The fix | ||
| // replaced the pre-check with `isSchemaPathAllowed`. These tests pin | ||
| // each path the original failure surfaced through. | ||
|
|
||
| it('permits the original failing path (`…buildArgs.SDK_TEST_DATA`)', () => { | ||
| expect( | ||
| Config.isSchemaPathAllowed( | ||
| 'platform.drive.abci.docker.build.buildArgs.SDK_TEST_DATA', | ||
| ), | ||
| ).to.be.true(); | ||
| }); | ||
|
|
||
| it('permits the same shape for rs-dapi (parallel Rust build)', () => { | ||
| expect( | ||
| Config.isSchemaPathAllowed( | ||
| 'platform.dapi.rsDapi.docker.build.buildArgs.CARGO_BUILD_PROFILE', | ||
| ), | ||
| ).to.be.true(); | ||
| }); | ||
|
|
||
| it('still permits the canonical typed paths that the pre-check used to handle', () => { | ||
| // Sanity: paths whose values DO exist after `dashmate setup local` — | ||
| // the original pre-check used to gate these via `config.get`. The | ||
| // schema walker must accept them too, or the CLI breaks for everyone. | ||
| for (const path of [ | ||
| 'platform.drive.abci.docker.build.enabled', | ||
| 'platform.drive.abci.docker.image', | ||
| 'platform.dapi.rsDapi.docker.build.enabled', | ||
| 'core.insight.enabled', | ||
| ]) { | ||
| expect(Config.isSchemaPathAllowed(path), `path: ${path}`).to.be.true(); | ||
| } | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reject dot-paths with empty segments.
At Line 90,
path.split('.')allows empty segments (e.g., trailing dot), which can make invalid keys schema-allowed underadditionalProperties. Add an explicit empty-segment check.Suggested fix
static isSchemaPathAllowed(path) { if (typeof path !== 'string' || path.length === 0) return false; + const segments = path.split('.'); + if (segments.some((segment) => segment.length === 0)) return false; @@ - for (const segment of path.split('.')) { + for (const segment of segments) {Also applies to: 90-91
🤖 Prompt for AI Agents