This commit is contained in:
jarek committed 2026-05-09 09:55:18 +02:00
1 parent 91ef3e3c9b
commit 002d969a5d
46 files changed
+7202 -233

No files matched your search

+26 -20
View File
@@ -102,6 +102,7 @@ export interface DeployStackOptions {
sourceDir?: string; // Directory to copy all files from (for git stacks)
forceRecreate?: boolean;
build?: boolean; // Build images before starting (--build)
noBuildCache?: boolean; // Disable build cache (--no-cache, requires --build)
pullPolicy?: string; // Pull policy: 'always' | 'missing' | 'never'
composePath?: string; // Custom compose file path (for adopted/imported stacks)
envPath?: string; // Custom env file path (for adopted/imported stacks)
@@ -259,23 +260,14 @@ async function readDirFilesAsMap(dirPath: string): Promise<Record<string, string
// =============================================================================
/**
* Mask sensitive values in environment variables for safe logging.
* Masks values for keys containing common secret patterns and truncates long values.
* Redact all env var values for safe logging. Only key names are preserved.
*/
function maskSecrets(vars: Record<string, string>): Record<string, string> {
const masked: Record<string, string> = {};
const secretPatterns = /password|secret|token|key|api_key|apikey|auth|credential|private/i;
for (const [key, value] of Object.entries(vars)) {
if (secretPatterns.test(key)) {
masked[key] = '***';
} else if (value.length > 50) {
// Truncate long values that might be secrets
masked[key] = value.substring(0, 10) + '...(truncated)';
} else {
masked[key] = value;
}
function redactEnvVarsForLog(vars: Record<string, string>): Record<string, string> {
const redacted: Record<string, string> = {};
for (const key of Object.keys(vars)) {
redacted[key] = '***';
}
return masked;
return redacted;
}
// =============================================================================
@@ -794,6 +786,7 @@ interface ComposeCommandOptions {
envId?: number | null;
forceRecreate?: boolean;
build?: boolean; // Build images before starting (--build)
noBuildCache?: boolean; // Disable build cache (--no-cache, requires --build)
pullPolicy?: string; // Pull policy: 'always' | 'missing' | 'never'
removeVolumes?: boolean;
stackFiles?: Record<string, string>; // All files to send to Hawser
@@ -857,6 +850,7 @@ async function executeLocalCompose(
useOverrideFile?: boolean,
serviceName?: string,
build?: boolean,
noBuildCache?: boolean,
pullPolicy?: string
): Promise<StackOperationResult> {
const logPrefix = `[Stack:${stackName}]`;
@@ -1050,6 +1044,7 @@ async function executeLocalCompose(
args.push('up', '-d', '--remove-orphans');
if (forceRecreate) args.push('--force-recreate');
if (build) args.push('--build');
if (build && noBuildCache) args.push('--no-cache');
if (pullPolicy) args.push('--pull', pullPolicy);
// If targeting a specific service, only update that service
if (serviceName) {
@@ -1094,7 +1089,7 @@ async function executeLocalCompose(
console.log(`${logPrefix} Service name:`, serviceName ?? '(all services)');
console.log(`${logPrefix} Env vars count:`, envVars ? Object.keys(envVars).length : 0);
if (envVars && Object.keys(envVars).length > 0) {
console.log(`${logPrefix} Env vars being injected (masked):`, JSON.stringify(maskSecrets(envVars), null, 2));
console.log(`${logPrefix} Env vars being injected (masked):`, JSON.stringify(redactEnvVarsForLog(envVars), null, 2));
}
// Login to registries before pulling images
@@ -1226,6 +1221,7 @@ async function executeComposeViaHawser(
serviceName?: string,
composeFileName?: string,
build?: boolean,
noBuildCache?: boolean,
pullPolicy?: string
): Promise<StackOperationResult> {
const logPrefix = `[Stack:${stackName}]`;
@@ -1249,7 +1245,7 @@ async function executeComposeViaHawser(
console.log(`${logPrefix} Non-secret env vars count:`, envVars ? Object.keys(envVars).length : 0);
console.log(`${logPrefix} Secret env vars count:`, secretCount);
if (allEnvVars && Object.keys(allEnvVars).length > 0) {
console.log(`${logPrefix} All env vars being sent (masked):`, JSON.stringify(maskSecrets(allEnvVars), null, 2));
console.log(`${logPrefix} All env vars being sent (masked):`, JSON.stringify(redactEnvVarsForLog(allEnvVars), null, 2));
}
console.log(`${logPrefix} Compose content length:`, composeContent.length, 'chars');
console.log(`${logPrefix} Stack files count:`, stackFiles ? Object.keys(stackFiles).length : 0);
@@ -1300,6 +1296,7 @@ async function executeComposeViaHawser(
forceRecreate: forceRecreate || false,
removeVolumes: removeVolumes || false,
build: build || false,
noBuildCache: (build && noBuildCache) || false,
pullPolicy: pullPolicy || '',
registries, // Registry credentials for docker login
serviceName // Target specific service only (with --no-deps)
@@ -1368,7 +1365,7 @@ async function executeComposeCommand(
envVars?: Record<string, string>,
secretVars?: Record<string, string>
): Promise<StackOperationResult> {
const { stackName, envId, forceRecreate, build, pullPolicy, removeVolumes, stackFiles, workingDir, composePath, envPath, useOverrideFile, serviceName, composeFileName } = options;
const { stackName, envId, forceRecreate, build, noBuildCache, pullPolicy, removeVolumes, stackFiles, workingDir, composePath, envPath, useOverrideFile, serviceName, composeFileName } = options;
// Get environment configuration
const env = envId ? await getEnvironment(envId) : null;
@@ -1392,6 +1389,7 @@ async function executeComposeCommand(
useOverrideFile,
serviceName,
build,
noBuildCache,
pullPolicy
);
}
@@ -1456,6 +1454,7 @@ async function executeComposeCommand(
serviceName,
composeFileName,
build,
noBuildCache,
pullPolicy
);
}
@@ -1489,6 +1488,7 @@ async function executeComposeCommand(
useOverrideFile,
serviceName,
build,
noBuildCache,
pullPolicy
);
}
@@ -1512,6 +1512,7 @@ async function executeComposeCommand(
useOverrideFile,
serviceName,
build,
noBuildCache,
pullPolicy
);
}
@@ -2175,7 +2176,7 @@ export async function removeStack(
* Uses stack locking to prevent concurrent deployments.
*/
export async function deployStack(options: DeployStackOptions): Promise<StackOperationResult> {
const { name, compose, envId, sourceDir, forceRecreate, build, pullPolicy, composePath, envPath, composeFileName, envFileName } = options;
const { name, compose, envId, sourceDir, forceRecreate, build, noBuildCache, pullPolicy, composePath, envPath, composeFileName, envFileName } = options;
const logPrefix = `[Stack:${name}]`;
console.log(`${logPrefix} ========================================`);
@@ -2253,7 +2254,11 @@ export async function deployStack(options: DeployStackOptions): Promise<StackOpe
// and would be destroyed, causing data loss (#831).
console.log(`${logPrefix} Copying source directory to stack directory...`);
mkdirSync(workingDir, { recursive: true });
cpSync(sourceDir, workingDir, { recursive: true, force: true });
cpSync(sourceDir, workingDir, {
recursive: true,
force: true,
filter: (src) => !src.includes('/.git/') && !src.endsWith('/.git')
});
console.log(`${logPrefix} Copied ${sourceDir} -> ${workingDir}`);
} else {
// Internal stack: check if a custom path exists in DB (adopted/imported stacks)
@@ -2318,6 +2323,7 @@ export async function deployStack(options: DeployStackOptions): Promise<StackOpe
envId,
forceRecreate,
build,
noBuildCache,
pullPolicy,
stackFiles,
workingDir,