optional Webhook column on the stacks list with a git stack's webhook id and copy-URL button
This commit is contained in:
4 files changed
+91
-12
No files matched your search
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Build the `.env.dockhand` override-file contents for a git stack.
|
||||
*
|
||||
* A git stack's compose commonly references `env_file: [.env.dockhand]` (the
|
||||
* documented way to consume Dockhand-managed non-secret vars). The file must exist
|
||||
* for `docker compose up` to parse - even when there are ZERO panel vars, in which
|
||||
* case it is just the header line (#1336). Pure: no FS, no DB, so both the local
|
||||
* (writeFileSync) and Hawser (in-memory stackFiles) deploy paths share one source of
|
||||
* truth and are unit-testable.
|
||||
*/
|
||||
|
||||
const HEADER = '# Auto-generated by Dockhand. Do not edit - changes will be overwritten on next deploy.\n';
|
||||
|
||||
export function buildDockhandOverrideFile(envVars: Record<string, string>): string {
|
||||
const lines = Object.entries(envVars).map(([k, v]) => `${k}=${v}`);
|
||||
return HEADER + lines.join('\n') + (lines.length ? '\n' : '');
|
||||
}
|
||||
+12
-12
@@ -51,6 +51,7 @@ import { cleanPem } from '$lib/utils/pem';
|
||||
import { rewriteComposeVolumePaths, getHostDataDir } from './host-path';
|
||||
import { getOrderValue } from './container-labels';
|
||||
import { pendingRowsToClear } from './pending-updates-core';
|
||||
import { buildDockhandOverrideFile } from './dockhand-override-file';
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
@@ -1247,17 +1248,17 @@ async function executeLocalCompose(
|
||||
args.push('--env-file', customEnvPath);
|
||||
}
|
||||
|
||||
// For git stacks: write non-secret overrides to .env.dockhand and add as second --env-file
|
||||
// For git stacks: write non-secret overrides to .env.dockhand and add as second --env-file.
|
||||
// Docker Compose applies env files in order, so later files override earlier ones.
|
||||
// This lets the repo's .env provide defaults while our overrides take precedence.
|
||||
// Secrets are still injected via shell env only (never written to disk).
|
||||
// Only written when useOverrideFile is true (git stacks). Internal/adopted stacks
|
||||
// already have their non-secrets in the .env file written by the UI.
|
||||
// ONLY when there ARE overrides: any --env-file on the CLI suppresses Compose's
|
||||
// auto-discovery of the adjacent default .env, so writing one for a zero-var stack
|
||||
// would blank a subdir compose's own .env interpolation (#1136). Internal/adopted
|
||||
// stacks already have their non-secrets in the .env file written by the UI.
|
||||
if (useOverrideFile && envVars && Object.keys(envVars).length > 0) {
|
||||
const overrideEnvPath = join(composeFileDir, '.env.dockhand');
|
||||
const header = '# Auto-generated by Dockhand. Do not edit - changes will be overwritten on next deploy.\n';
|
||||
const lines = Object.entries(envVars).map(([k, v]) => `${k}=${v}`);
|
||||
writeFileSync(overrideEnvPath, header + lines.join('\n') + '\n');
|
||||
writeFileSync(overrideEnvPath, buildDockhandOverrideFile(envVars));
|
||||
args.push('--env-file', overrideEnvPath);
|
||||
}
|
||||
|
||||
@@ -1705,13 +1706,12 @@ async function executeComposeCommand(
|
||||
}
|
||||
}
|
||||
|
||||
// For git stacks: generate .env.dockhand with non-secret DB overrides
|
||||
// This mirrors executeLocalCompose behavior (lines 1017-1023).
|
||||
// envVars contains only the DB overrides (not merged repo .env values from hawserEnvVars).
|
||||
// For git stacks: generate .env.dockhand with non-secret DB overrides.
|
||||
// ONLY when there ARE overrides: the agent adds it as --env-file, which
|
||||
// suppresses Compose's adjacent-.env auto-discovery, so an empty file would
|
||||
// blank a subdir compose's own .env interpolation (#1136).
|
||||
if (useOverrideFile && envVars && Object.keys(envVars).length > 0) {
|
||||
const header = '# Auto-generated by Dockhand. Do not edit - changes will be overwritten on next deploy.\n';
|
||||
const lines = Object.entries(envVars).map(([k, v]) => `${k}=${v}`);
|
||||
hawserStackFiles = { ...(hawserStackFiles || {}), '.env.dockhand': header + lines.join('\n') + '\n' };
|
||||
hawserStackFiles = { ...(hawserStackFiles || {}), '.env.dockhand': buildDockhandOverrideFile(envVars) };
|
||||
console.log(`[Stack:${stackName}] Including .env.dockhand override file for Hawser (${Object.keys(envVars).length} vars)`);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* buildDockhandOverrideFile: the .env.dockhand contents shared by both git-stack
|
||||
* deploy paths (local writeFileSync + Hawser stackFiles). A zero-var git stack must
|
||||
* still produce a (header-only) file so a compose referencing env_file: .env.dockhand
|
||||
* can resolve it (#1336).
|
||||
*/
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { buildDockhandOverrideFile } from '../src/lib/server/dockhand-override-file';
|
||||
|
||||
describe('buildDockhandOverrideFile', () => {
|
||||
it('returns a header-only file (no trailing blank line) for ZERO vars', () => {
|
||||
const out = buildDockhandOverrideFile({});
|
||||
expect(out).toBe(
|
||||
'# Auto-generated by Dockhand. Do not edit - changes will be overwritten on next deploy.\n'
|
||||
);
|
||||
// exactly one trailing newline - no empty value line that could clear a repo var
|
||||
expect(out.endsWith('\n')).toBe(true);
|
||||
expect(out.endsWith('\n\n')).toBe(false);
|
||||
});
|
||||
|
||||
it('writes KEY=value lines under the header', () => {
|
||||
const out = buildDockhandOverrideFile({ A: '1', B: 'two' });
|
||||
expect(out).toContain('A=1');
|
||||
expect(out).toContain('B=two');
|
||||
expect(out.startsWith('# Auto-generated')).toBe(true);
|
||||
expect(out.endsWith('\n')).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps values verbatim (including = and spaces)', () => {
|
||||
const out = buildDockhandOverrideFile({ URL: 'https://x/y?a=b c' });
|
||||
expect(out).toContain('URL=https://x/y?a=b c');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* A column with defaultVisible:false is hidden by default (user can enable it in
|
||||
* grid preferences); every other column stays visible. The stacks 'webhook' column
|
||||
* relies on this so it does not clutter the list for users without git webhooks (#845).
|
||||
*/
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { getDefaultColumnPreferences, getConfigurableColumns } from '../src/lib/config/grid-columns';
|
||||
|
||||
describe('getDefaultColumnPreferences honors defaultVisible', () => {
|
||||
it('hides a column marked defaultVisible:false', () => {
|
||||
const prefs = getDefaultColumnPreferences('stacks');
|
||||
const webhook = prefs.find((p) => p.id === 'webhook');
|
||||
expect(webhook).toBeDefined();
|
||||
expect(webhook!.visible).toBe(false);
|
||||
});
|
||||
|
||||
it('leaves all other configurable columns visible', () => {
|
||||
const prefs = getDefaultColumnPreferences('stacks');
|
||||
for (const p of prefs) {
|
||||
if (p.id === 'webhook') continue;
|
||||
expect(p.visible).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('the webhook column is configurable (user can toggle it), not fixed', () => {
|
||||
const configurable = getConfigurableColumns('stacks').map((c) => c.id);
|
||||
expect(configurable).toContain('webhook');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user