Skip to content

Commit 0285590

Browse files
Support escaped colon in variables (#336)
1 parent 248940a commit 0285590

File tree

5 files changed

+89
-7
lines changed

5 files changed

+89
-7
lines changed

source/tasks/Deploy/DeployV6/inputCommandBuilder.test.ts

+27
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,33 @@ describe("getInputCommand", () => {
6060
expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2" });
6161
});
6262

63+
test("handles escaped colons in variable names", () => {
64+
task.addVariableString("Space", "Default");
65+
task.addVariableString("Variables", "Test\\:Variable: Testing3");
66+
task.addVariableString("Environments", "test");
67+
task.addVariableString("Project", "Test project");
68+
task.addVariableString("ReleaseNumber", "1.0.0");
69+
task.addVariableString("DeployForTenants", "Tenant 1");
70+
71+
const command = createCommandFromInputs(logger, task);
72+
expect(command.Variables).toStrictEqual({ "Test:Variable": "Testing3" });
73+
});
74+
75+
test("handles multiple variables with escaped and unescaped colons", () => {
76+
task.addVariableString("Space", "Default");
77+
task.addVariableString("Variables", "Long\\:Variable\\:Name: Value123\nTest\\:Variable: Value: With: Colons");
78+
task.addVariableString("Environments", "test");
79+
task.addVariableString("Project", "Test project");
80+
task.addVariableString("ReleaseNumber", "1.0.0");
81+
task.addVariableString("DeployForTenants", "Tenant 1");
82+
83+
const command = createCommandFromInputs(logger, task);
84+
expect(command.Variables).toStrictEqual({
85+
"Long:Variable:Name": "Value123",
86+
"Test:Variable": "Value: With: Colons"
87+
});
88+
});
89+
6390
test("multiline environments", () => {
6491
task.addVariableString("Space", "Default");
6592
task.addVariableString("Environments", "dev, test\nprod");

source/tasks/Deploy/DeployV6/inputCommandBuilder.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import commandLineArgs from "command-line-args";
22
import shlex from "shlex";
3-
import { getLineSeparatedItems } from "../../Utils/inputs";
3+
import { getLineSeparatedItems, parseVariableString } from "../../Utils/inputs";
44
import { CreateDeploymentUntenantedCommandV1, Logger, PromptedVariableValues } from "@octopusdeploy/api-client";
55
import { TaskWrapper } from "tasks/Utils/taskInput";
66

@@ -22,13 +22,13 @@ export function createCommandFromInputs(logger: Logger, task: TaskWrapper): Crea
2222
}
2323

2424
const variablesField = task.getInput("Variables");
25-
logger.debug?.("Variables:" + variablesField);
25+
logger.debug?.("Variables: " + variablesField);
2626
if (variablesField) {
2727
const variables = getLineSeparatedItems(variablesField).map((p) => p.trim()) || undefined;
2828
if (variables) {
2929
for (const variable of variables) {
30-
const variableMap = variable.split(":").map((x) => x.trim());
31-
variablesMap[variableMap[0]] = variableMap[1];
30+
const [name, value] = parseVariableString(variable);
31+
variablesMap[name] = value;
3232
}
3333
}
3434
}

source/tasks/DeployTenant/TenantedDeployV6/inputCommandBuilder.test.ts

+27
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,33 @@ describe("getInputCommand", () => {
6565
expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2" });
6666
});
6767

68+
test("handles escaped colons in variable names", () => {
69+
task.addVariableString("Space", "Default");
70+
task.addVariableString("Variables", "Test\\:Variable: Testing3");
71+
task.addVariableString("Environment", "dev");
72+
task.addVariableString("Project", "Test project");
73+
task.addVariableString("ReleaseNumber", "1.0.0");
74+
task.addVariableString("DeployForTenants", "Tenant 1");
75+
76+
const command = createCommandFromInputs(logger, task);
77+
expect(command.Variables).toStrictEqual({ "Test:Variable": "Testing3" });
78+
});
79+
80+
test("handles multiple variables with escaped and unescaped colons", () => {
81+
task.addVariableString("Space", "Default");
82+
task.addVariableString("Variables", "Long\\:Variable\\:Name: Value123\nTest\\:Variable: Value: With: Colons");
83+
task.addVariableString("Environment", "dev");
84+
task.addVariableString("Project", "Test project");
85+
task.addVariableString("ReleaseNumber", "1.0.0");
86+
task.addVariableString("DeployForTenants", "Tenant 1");
87+
88+
const command = createCommandFromInputs(logger, task);
89+
expect(command.Variables).toStrictEqual({
90+
"Long:Variable:Name": "Value123",
91+
"Test:Variable": "Value: With: Colons"
92+
});
93+
});
94+
6895
test("validate tenants and tags", () => {
6996
task.addVariableString("Space", "Default");
7097
task.addVariableString("Project", "project 1");

source/tasks/DeployTenant/TenantedDeployV6/inputCommandBuilder.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import commandLineArgs from "command-line-args";
22
import shlex from "shlex";
3-
import { getLineSeparatedItems } from "../../Utils/inputs";
3+
import { getLineSeparatedItems, parseVariableString } from "../../Utils/inputs";
44
import { CreateDeploymentTenantedCommandV1, Logger, PromptedVariableValues } from "@octopusdeploy/api-client";
55
import { TaskWrapper } from "tasks/Utils/taskInput";
66

@@ -32,8 +32,8 @@ export function createCommandFromInputs(logger: Logger, task: TaskWrapper): Crea
3232
const variables = getLineSeparatedItems(variablesField).map((p) => p.trim()) || undefined;
3333
if (variables) {
3434
for (const variable of variables) {
35-
const variableMap = variable.split(":").map((x) => x.trim());
36-
variablesMap[variableMap[0]] = variableMap[1];
35+
const [name, value] = parseVariableString(variable);
36+
variablesMap[name] = value;
3737
}
3838
}
3939
}

source/tasks/Utils/inputs.ts

+28
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,34 @@ export function getLineSeparatedItems(value: string): Array<string> {
1212
return value ? value.split(/[\r\n]+/g).map((x) => x.trim()) : [];
1313
}
1414

15+
export function parseVariableString(input: string): [string, string] {
16+
let escapeNext = false;
17+
let colonIndex = -1;
18+
19+
for (let i = 0; i < input.length; i++) {
20+
if (input[i] === '\\' && !escapeNext) {
21+
escapeNext = true;
22+
continue;
23+
}
24+
25+
if (input[i] === ':' && !escapeNext) {
26+
colonIndex = i;
27+
break;
28+
}
29+
30+
escapeNext = false;
31+
}
32+
33+
if (colonIndex === -1) {
34+
throw new Error(`Invalid variable format. Expected 'name: value' but got '${input}'`);
35+
}
36+
37+
const variableName = input.substring(0, colonIndex).replace(/\\:/g, ':').trim();
38+
const variableValue = input.substring(colonIndex + 1).trim();
39+
40+
return [variableName, variableValue];
41+
}
42+
1543
export function getOverwriteModeFromReplaceInput(replace: string): ReplaceOverwriteMode {
1644
return ReplaceOverwriteMode[replace as keyof typeof ReplaceOverwriteMode] || ReplaceOverwriteMode.false;
1745
}

0 commit comments

Comments
 (0)