diff --git a/core/src/plugins/kubernetes/sync.ts b/core/src/plugins/kubernetes/sync.ts index 283a32196b..8df1274563 100644 --- a/core/src/plugins/kubernetes/sync.ts +++ b/core/src/plugins/kubernetes/sync.ts @@ -599,7 +599,11 @@ export async function startSyncs(params: StartSyncsParams) { ) const allSyncs = expectedKeys.length === 0 ? [] : await mutagen.getActiveSyncSessions() - const keyPrefix = getSyncKeyPrefix(ctx, action) + const keyPrefix = getSyncKeyPrefix({ + environmentName: ctx.environmentName, + namespace: ctx.namespace, + actionName: action.name, + }) for (const sync of allSyncs.filter((s) => s.name.startsWith(keyPrefix) && !expectedKeys.includes(s.name))) { log.info(`Terminating unexpected/outdated sync ${sync.name}`) @@ -615,7 +619,11 @@ export async function stopSyncs(params: StopSyncsParams) { const mutagen = new Mutagen({ ctx, log }) const allSyncs = await mutagen.getActiveSyncSessions() - const keyPrefix = getSyncKeyPrefix(ctx, action) + const keyPrefix = getSyncKeyPrefix({ + environmentName: ctx.environmentName, + namespace: ctx.namespace, + actionName: action.name, + }) const syncs = allSyncs.filter((sync) => sync.name.startsWith(keyPrefix)) for (const sync of syncs) { @@ -775,7 +783,11 @@ export async function getSyncStatus(params: GetSyncStatusParams): Promise | :). + * It cannot contain any characters that can break the command execution (like / \ < > | :). + * + * Note, that function {@link kebabCase} replaces any sequence of multiple dashes with a single dash character. + * + * It's critical to have double dashes (--) as a delimiter of a key parts here and in {@link getSyncKeyPrefix} + * to avoid potential collisions of the sync key prefixes. */ -function getSyncKey({ ctx, action, spec }: PrepareSyncParams, target: SyncableResource): string { +export function getSyncKey({ ctx, action, spec }: PrepareSyncParams, target: SyncableResource): string { const sourcePath = relative(action.sourcePath(), spec.sourcePath) const containerPath = spec.containerPath - return kebabCase( - `${getSyncKeyPrefix(ctx, action)}${target.kind}--${target.metadata.name}--${sourcePath}--${containerPath}` - ) + // Kebab-case each part of the key prefix separately to preserve double-dash delimiters + return `${getSyncKeyPrefix({ + environmentName: ctx.environmentName, + namespace: ctx.namespace, + actionName: action.name, + })}${kebabCase(target.kind)}--${kebabCase(target.metadata.name)}--${kebabCase(sourcePath)}--${kebabCase( + containerPath + )}` } async function prepareSync(params: PrepareSyncParams) { diff --git a/core/test/unit/src/plugins/kubernetes/dev-mode.ts b/core/test/unit/src/plugins/kubernetes/sync-mode.ts similarity index 79% rename from core/test/unit/src/plugins/kubernetes/dev-mode.ts rename to core/test/unit/src/plugins/kubernetes/sync-mode.ts index 19493fdb81..f6ca75d9ad 100644 --- a/core/test/unit/src/plugins/kubernetes/dev-mode.ts +++ b/core/test/unit/src/plugins/kubernetes/sync-mode.ts @@ -7,7 +7,12 @@ */ import { expect } from "chai" -import { builtInExcludes, getLocalSyncPath, makeSyncConfig } from "../../../../../src/plugins/kubernetes/sync.js" +import { + builtInExcludes, + getLocalSyncPath, + getSyncKeyPrefix, + makeSyncConfig, +} from "../../../../../src/plugins/kubernetes/sync.js" describe("k8s sync helpers", () => { describe("getLocalSyncPath", () => { @@ -141,4 +146,25 @@ describe("k8s sync helpers", () => { }) }) }) + + describe("getSyncKeyPrefix", () => { + const environmentName = "dev" + const namespace = "default" + + it("produces a sync key prefix with double-dashes", () => { + const syncKeyPrefix = getSyncKeyPrefix({ environmentName, namespace, actionName: "backend" }) + expect(syncKeyPrefix).to.eql("k8s--dev--default--backend--") + }) + + it("produces non-colliding keys if one action's name starts with another action's name", () => { + const actionName1 = "backend" + const actionName2 = "backend-new" + expect(actionName2.startsWith(actionName1)).to.be.true + + const syncKeyPrefix1 = getSyncKeyPrefix({ environmentName, namespace, actionName: actionName1 }) + const syncKeyPrefix2 = getSyncKeyPrefix({ environmentName, namespace, actionName: actionName2 }) + expect(syncKeyPrefix2.startsWith(syncKeyPrefix1)).to.be.false + expect(syncKeyPrefix1.startsWith(syncKeyPrefix2)).to.be.false + }) + }) })