From 4c73be5e45776e283fd2dc538a617e4220eb1fbf Mon Sep 17 00:00:00 2001 From: Nick Josevski Date: Wed, 12 Aug 2026 13:38:34 +1000 Subject: [PATCH 1/4] fix: additional command line arguments dropped by argument filtering The "Additional command line arguments" field is shared by every command a step runs, so each command strips the arguments belonging to the other. Two bugs in that filtering silently discarded valid arguments: - Switches that take no value (--update-variables, --guided-failure, --ignore-existing, ...) consumed the token that followed them. So "--ignore-existing --variable ImageTag:1.5.2" left the deploy command with a bare "ImageTag:1.5.2" and no --variable at all. - Matching used String.contains against a comma-joined list, so --var and --e were stripped as substrings of --variable and --environment. Match argument names exactly against a Set, and only consume the following token as a value when it does not itself start with "-". Co-Authored-By: Claude Opus 5 (1M context) --- .../teamcity/agent/cli/CommandHelper.java | 100 +++++++++++++++--- .../teamcity/agent/cli/CommandHelperTest.java | 70 +++++++++++- 2 files changed, 154 insertions(+), 16 deletions(-) diff --git a/octopus-agent/src/main/java/octopus/teamcity/agent/cli/CommandHelper.java b/octopus-agent/src/main/java/octopus/teamcity/agent/cli/CommandHelper.java index 3d62596c..40fe9092 100644 --- a/octopus-agent/src/main/java/octopus/teamcity/agent/cli/CommandHelper.java +++ b/octopus-agent/src/main/java/octopus/teamcity/agent/cli/CommandHelper.java @@ -6,8 +6,12 @@ import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import jetbrains.buildServer.agent.BuildProgressLogger; import jetbrains.buildServer.agent.impl.artifacts.ArtifactsCollection; @@ -20,12 +24,50 @@ public class CommandHelper { - static final String deployReleaseAdditionalArgumentsToBeIgnored = - "-e,--environment,--tenant,--tenant-tag,--deploy-at,--deploy-at-expiry,--variable,--update-variables,--skip,--guided-failure,--force-package-download,--deployment-target,--exclude-deployment-target,--deployment-freeze-name,--deployment-freeze-override-reason"; - static final String runbookRunAdditionalArgumentsToBeIgnored = - "-p,--project,-n,--name,-e,--environment,--tenant,--tenant-tag,--snapshot,--space"; - static final String createReleaseAdditionalArgumentsToBeIgnored = - "-c,-r,-x,--channel,--git-ref,--git-commit,--package,--git-resource,--release-notes,--release-notes-file,--ignore-existing,--ignore-channel-rules,--custom-field"; + static final Set deployReleaseAdditionalArgumentsToBeIgnored = + argumentSet( + "-e", + "--environment", + "--tenant", + "--tenant-tag", + "--deploy-at", + "--deploy-at-expiry", + "--variable", + "--update-variables", + "--skip", + "--guided-failure", + "--force-package-download", + "--deployment-target", + "--exclude-deployment-target", + "--deployment-freeze-name", + "--deployment-freeze-override-reason"); + static final Set runbookRunAdditionalArgumentsToBeIgnored = + argumentSet( + "-p", + "--project", + "-n", + "--name", + "-e", + "--environment", + "--tenant", + "--tenant-tag", + "--snapshot", + "--space"); + static final Set createReleaseAdditionalArgumentsToBeIgnored = + argumentSet( + "-c", + "-r", + "-x", + "--channel", + "--git-ref", + "--git-commit", + "--package", + "--git-resource", + "--release-notes", + "--release-notes-file", + "--ignore-existing", + "--ignore-channel-rules", + "--custom-field"); static final String defaultSpace = "Default"; public static String[] deployRelease( @@ -427,17 +469,49 @@ public static String[] wait(Map params, String taskId) { return commands.toArray(new String[0]); } - public static List sanitizeCommandArgs(List args, String argsToBeIgnore) { - List result = new ArrayList<>(); + /** + * Removes the arguments in {@code argsToBeIgnored} - along with their values - from a list of + * user-supplied additional command line arguments. + * + *

The "Additional command line arguments" field is shared by every command a step runs, so + * each command has to drop the arguments that belong to the other. Matching is exact: a substring + * match would strip {@code --var} because {@code --variable} is on the list. A value is only + * consumed when the following token is not itself an argument, so switches that take no value + * (such as {@code --update-variables}) no longer swallow the argument that follows them. + * + *

The consequence is that a value which legitimately starts with {@code -} is left behind as a + * stray positional argument. That is rare, and preferable to silently dropping a real argument. + */ + public static List sanitizeCommandArgs( + final List args, final Set argsToBeIgnored) { + final List result = new ArrayList<>(); int i = 0; while (i < args.size()) { - if (argsToBeIgnore.contains(args.get(i))) { - i += 2; // skip this element and the next element - } else { - result.add(args.get(i)); - i++; + final String arg = args.get(i); + i++; + if (!isIgnored(arg, argsToBeIgnored)) { + result.add(arg); + } else if (!hasInlineValue(arg) && i < args.size() && !isArgumentName(args.get(i))) { + i++; // the following token is this argument's value } } return result; } + + private static Set argumentSet(final String... args) { + return Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(args))); + } + + private static boolean isIgnored(final String arg, final Set argsToBeIgnored) { + final int equals = arg.indexOf('='); + return argsToBeIgnored.contains(equals > 0 ? arg.substring(0, equals) : arg); + } + + private static boolean hasInlineValue(final String arg) { + return arg.indexOf('=') > 0; + } + + private static boolean isArgumentName(final String token) { + return token.startsWith("-"); + } } diff --git a/octopus-agent/src/test/java/octopus/teamcity/agent/cli/CommandHelperTest.java b/octopus-agent/src/test/java/octopus/teamcity/agent/cli/CommandHelperTest.java index 0a311a4f..0ac975c6 100644 --- a/octopus-agent/src/test/java/octopus/teamcity/agent/cli/CommandHelperTest.java +++ b/octopus-agent/src/test/java/octopus/teamcity/agent/cli/CommandHelperTest.java @@ -9,8 +9,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import jetbrains.buildServer.agent.AgentRunningBuild; import jetbrains.buildServer.agent.BuildRunnerContext; @@ -357,12 +359,16 @@ void loginCommandUsesApiKeyForNonOidcSource() { assertThat(command).doesNotContain("--service-account-id", "--id-token"); } + private static Set ignoring(String... args) { + return new LinkedHashSet<>(Arrays.asList(args)); + } + @Test void sanitizeCommandExtraArgsRemovesForbiddenArgAndValue() { List args = Arrays.asList("project", "MyProject", "channel", "Release", "version", "1.0.0"); - List result = CommandHelper.sanitizeCommandArgs(args, "channel"); + List result = CommandHelper.sanitizeCommandArgs(args, ignoring("channel")); assertThat(result).containsExactly("project", "MyProject", "version", "1.0.0"); } @@ -373,7 +379,7 @@ void sanitizeCommandExtraArgsRemovesMultipleForbiddenArgs() { Arrays.asList( "project", "MyProject", "channel", "Release", "tenant", "TenantA", "version", "1.0.0"); - List result = CommandHelper.sanitizeCommandArgs(args, "channel, tenant"); + List result = CommandHelper.sanitizeCommandArgs(args, ignoring("channel", "tenant")); assertThat(result).containsExactly("project", "MyProject", "version", "1.0.0"); } @@ -382,8 +388,66 @@ void sanitizeCommandExtraArgsRemovesMultipleForbiddenArgs() { void sanitizeCommandExtraArgsKeepsArgsWhenNoForbiddenPresent() { List args = Arrays.asList("project", "MyProject", "version", "1.0.0"); - List result = CommandHelper.sanitizeCommandArgs(args, "channel"); + List result = CommandHelper.sanitizeCommandArgs(args, ignoring("channel")); assertThat(result).containsExactly("project", "MyProject", "version", "1.0.0"); } + + @Test + void sanitizeCommandExtraArgsDoesNotConsumeArgumentAfterAValuelessSwitch() { + List args = Arrays.asList("--ignore-existing", "--variable", "ImageTag:1.5.2"); + + List result = + CommandHelper.sanitizeCommandArgs( + args, CommandHelper.createReleaseAdditionalArgumentsToBeIgnored); + + assertThat(result).containsExactly("--variable", "ImageTag:1.5.2"); + } + + @Test + void sanitizeCommandExtraArgsDoesNotLeaveAStrayValueBehind() { + List args = Arrays.asList("--update-variables", "--channel", "Beta"); + + List result = + CommandHelper.sanitizeCommandArgs( + args, CommandHelper.deployReleaseAdditionalArgumentsToBeIgnored); + + assertThat(result).containsExactly("--channel", "Beta"); + } + + @Test + void sanitizeCommandExtraArgsMatchesArgumentNamesExactly() { + List args = Arrays.asList("--var", "Something", "--e", "SomethingElse"); + + List result = + CommandHelper.sanitizeCommandArgs( + args, CommandHelper.deployReleaseAdditionalArgumentsToBeIgnored); + + assertThat(result).containsExactly("--var", "Something", "--e", "SomethingElse"); + } + + @Test + void sanitizeCommandExtraArgsRemovesForbiddenArgWithAnInlineValue() { + List args = Arrays.asList("--variable=ImageTag:1.5.2", "--channel", "Beta"); + + List result = + CommandHelper.sanitizeCommandArgs( + args, CommandHelper.deployReleaseAdditionalArgumentsToBeIgnored); + + assertThat(result).containsExactly("--channel", "Beta"); + } + + @Test + void createReleaseWithDeployToForwardsPromptedVariablesToTheDeployCommand() { + Map params = new HashMap<>(); + final OctopusConstants constants = OctopusConstants.Instance; + params.put(constants.getProjectNameKey(), "MyProject"); + params.put(constants.getDeployToKey(), "Dev"); + params.put(constants.getCommandLineArgumentsKey(), "--variable ImageTag:1.5.2"); + + assertThat(CommandHelper.createRelease(params).buildCommand()) + .doesNotContain("--variable", "ImageTag:1.5.2"); + assertThat(CommandHelper.deployRelease(params, "1.0.0")) + .containsSequence("--variable", "ImageTag:1.5.2"); + } } From e52107261eb05a69a029ea4b4599b484555fc1ad Mon Sep 17 00:00:00 2001 From: Nick Josevski Date: Wed, 12 Aug 2026 13:38:38 +1000 Subject: [PATCH 2/4] fix: --variable silently ignored on Create release steps that do not deploy Prompted variables are supplied when a release is deployed. The Octopus CLI accepts --variable on a create-only command and then ignores it, so putting it in "Additional command line arguments" on a Create release step appears to do nothing, and the deployment later fails with "Please provide a variable for the prompted value". Reject that combination when the step is saved, pointing at either the "Deploy to" field on this step or a separate Deploy release step, and say the same thing in the field's hint. Fixes #204 Co-Authored-By: Claude Opus 5 (1M context) --- .../server/OctopusCreateReleaseRunType.java | 2 + .../teamcity/server/PropertiesValidator.java | 31 +++++++++++++++ .../forms/editOctopusCreateReleaseForm.jsp | 3 +- ...pusCreateReleaseRunTypeValidationTest.java | 39 +++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/octopus-server/src/main/java/octopus/teamcity/server/OctopusCreateReleaseRunType.java b/octopus-server/src/main/java/octopus/teamcity/server/OctopusCreateReleaseRunType.java index f4f10b2f..10106d2a 100644 --- a/octopus-server/src/main/java/octopus/teamcity/server/OctopusCreateReleaseRunType.java +++ b/octopus-server/src/main/java/octopus/teamcity/server/OctopusCreateReleaseRunType.java @@ -2,6 +2,7 @@ import static octopus.teamcity.server.PropertiesValidator.checkCredentialsUnlessUsingConnection; import static octopus.teamcity.server.PropertiesValidator.checkNotEmpty; +import static octopus.teamcity.server.PropertiesValidator.checkPromptedVariablesOnlyWhenDeploying; import java.util.ArrayList; import java.util.Collection; @@ -58,6 +59,7 @@ public Collection process(@Nullable final Map p checkCredentialsUnlessUsingConnection(p, c, result); checkNotEmpty(p, c.getProjectNameKey(), "Project name must be specified", result); + checkPromptedVariablesOnlyWhenDeploying(p, c, result); if (result.isEmpty()) { ConnectionInlineFieldCleaner.stripInlineFieldsIfUsingConnection(p); diff --git a/octopus-server/src/main/java/octopus/teamcity/server/PropertiesValidator.java b/octopus-server/src/main/java/octopus/teamcity/server/PropertiesValidator.java index 33d78de9..12fe8d78 100644 --- a/octopus-server/src/main/java/octopus/teamcity/server/PropertiesValidator.java +++ b/octopus-server/src/main/java/octopus/teamcity/server/PropertiesValidator.java @@ -17,6 +17,7 @@ import java.util.Collection; import java.util.Map; +import java.util.regex.Pattern; import jetbrains.buildServer.serverSide.InvalidProperty; import jetbrains.buildServer.util.StringUtil; @@ -26,6 +27,9 @@ /** Shared validation helpers for run type {@code PropertiesProcessor}s. */ public final class PropertiesValidator { + private static final Pattern VARIABLE_ARGUMENT = + Pattern.compile("(?:^|\\s)--variable(?:[\\s=]|$)"); + private PropertiesValidator() {} /** @@ -57,4 +61,31 @@ public static void checkCredentialsUnlessUsingConnection( checkNotEmpty(properties, constants.getApiKey(), "API key must be specified", result); } } + + /** + * Rejects {@code --variable} in the additional command line arguments of a step that only creates + * a release. Prompted variables are supplied when a release is deployed, so the Octopus CLI + * accepts the argument on a create-only command and then ignores it, leaving the deployment to + * fail later with "Please provide a variable for the prompted value". + */ + public static void checkPromptedVariablesOnlyWhenDeploying( + @NotNull final Map properties, + @NotNull final OctopusConstants constants, + @NotNull final Collection result) { + final String additionalArguments = properties.get(constants.getCommandLineArgumentsKey()); + if (StringUtil.isEmptyOrSpaces(additionalArguments)) { + return; + } + if (!VARIABLE_ARGUMENT.matcher(additionalArguments).find()) { + return; + } + if (StringUtil.isEmptyOrSpaces(properties.get(constants.getDeployToKey()))) { + result.add( + new InvalidProperty( + constants.getCommandLineArgumentsKey(), + "--variable supplies prompted variables to a deployment, so it has no effect on a " + + "step that only creates a release. Set 'Deploy to' on this step, or move " + + "--variable to an 'OctopusDeploy: Deploy release' step.")); + } + } } diff --git a/octopus-server/src/main/resources/buildServerResources/forms/editOctopusCreateReleaseForm.jsp b/octopus-server/src/main/resources/buildServerResources/forms/editOctopusCreateReleaseForm.jsp index 33424627..5c86057c 100644 --- a/octopus-server/src/main/resources/buildServerResources/forms/editOctopusCreateReleaseForm.jsp +++ b/octopus-server/src/main/resources/buildServerResources/forms/editOctopusCreateReleaseForm.jsp @@ -141,7 +141,8 @@ - Additional arguments to be passed to Octopus CLI + Additional arguments to be passed to Octopus CLI. + --variable supplies prompted variables to a deployment, so it only takes effect when Deploy to is also set on this step. diff --git a/octopus-server/src/test/java/octopus/teamcity/server/OctopusCreateReleaseRunTypeValidationTest.java b/octopus-server/src/test/java/octopus/teamcity/server/OctopusCreateReleaseRunTypeValidationTest.java index 0db5c39e..a943d531 100644 --- a/octopus-server/src/test/java/octopus/teamcity/server/OctopusCreateReleaseRunTypeValidationTest.java +++ b/octopus-server/src/test/java/octopus/teamcity/server/OctopusCreateReleaseRunTypeValidationTest.java @@ -53,4 +53,43 @@ void neitherConnectionNorManualIsInvalid() { final Map properties = withMandatoryNonCredentialFields(new HashMap<>()); assertThat(validate(properties)).contains(CONSTANTS.getServerKey(), CONSTANTS.getApiKey()); } + + private Map withAdditionalArguments(final String additionalArguments) { + final Map properties = withMandatoryNonCredentialFields(new HashMap<>()); + properties.put(CONSTANTS.getConnectionIdKey(), "PROJECT_EXT_1"); + properties.put(CONSTANTS.getCommandLineArgumentsKey(), additionalArguments); + return properties; + } + + @Test + void promptedVariableWithoutDeployToIsInvalid() { + assertThat(validate(withAdditionalArguments("--variable ImageTag:1.5.2"))) + .contains(CONSTANTS.getCommandLineArgumentsKey()); + } + + @Test + void promptedVariableWithAnInlineValueAndWithoutDeployToIsInvalid() { + assertThat(validate(withAdditionalArguments("--progress --variable=ImageTag:1.5.2"))) + .contains(CONSTANTS.getCommandLineArgumentsKey()); + } + + @Test + void promptedVariableWithDeployToIsValid() { + final Map properties = withAdditionalArguments("--variable ImageTag:1.5.2"); + properties.put(CONSTANTS.getDeployToKey(), "Dev"); + assertThat(validate(properties)).doesNotContain(CONSTANTS.getCommandLineArgumentsKey()); + } + + @Test + void otherArgumentsEndingInVariableAreValid() { + assertThat(validate(withAdditionalArguments("--update-variables --progress"))) + .doesNotContain(CONSTANTS.getCommandLineArgumentsKey()); + } + + @Test + void noAdditionalArgumentsIsValid() { + final Map properties = withMandatoryNonCredentialFields(new HashMap<>()); + properties.put(CONSTANTS.getConnectionIdKey(), "PROJECT_EXT_1"); + assertThat(validate(properties)).doesNotContain(CONSTANTS.getCommandLineArgumentsKey()); + } } From 86dba00be0d5586a4f6792eccb313f8a9637434f Mon Sep 17 00:00:00 2001 From: Nick Josevski Date: Thu, 20 Aug 2026 14:45:08 +1000 Subject: [PATCH 3/4] fix: accept --variable when the deploy target is a legacy --deployTo argument The legacy octo CLI takes the deploy target on create-release, so a step that supplies it through the additional arguments rather than the "Deploy to" field does deploy, and --variable does take effect. Verified against the octo 9.1.7 bundled in this repo: create-release --deployTo Development --variable ImageTag:7.7.7 creates the release, deploys it, and the prompted variable reaches the script. Reported by sathvikkumar-octo on #220. The check now fires only on a step that deploys via neither route. octo's option parser is case insensitive and accepts -, -- and / prefixes, so the pattern matches all of those forms. Which CLI will run is an agent-side decision, so this cannot be narrowed further server-side. On the new CLI --deployTo is not a "release create" argument at all and the step fails with "unknown flag", which is loud enough to diagnose without help from this check. Co-Authored-By: Claude Opus 5 (1M context) --- .../teamcity/server/PropertiesValidator.java | 19 ++++++++++++++++++ ...pusCreateReleaseRunTypeValidationTest.java | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/octopus-server/src/main/java/octopus/teamcity/server/PropertiesValidator.java b/octopus-server/src/main/java/octopus/teamcity/server/PropertiesValidator.java index 12fe8d78..61ad015d 100644 --- a/octopus-server/src/main/java/octopus/teamcity/server/PropertiesValidator.java +++ b/octopus-server/src/main/java/octopus/teamcity/server/PropertiesValidator.java @@ -30,6 +30,16 @@ public final class PropertiesValidator { private static final Pattern VARIABLE_ARGUMENT = Pattern.compile("(?:^|\\s)--variable(?:[\\s=]|$)"); + /** + * The legacy {@code octo} CLI takes the deploy target as {@code --deployTo} on {@code + * create-release}, and its option parser is case insensitive and accepts {@code -}, {@code --} + * and {@code /} prefixes. A step that supplies the target that way rather than through the + * "Deploy to" field does deploy, so {@code --variable} takes effect and must not be reported as + * useless. + */ + private static final Pattern DEPLOY_TO_ARGUMENT = + Pattern.compile("(?:^|\\s)(?:--?|/)deployto(?:[\\s=]|$)", Pattern.CASE_INSENSITIVE); + private PropertiesValidator() {} /** @@ -67,6 +77,12 @@ public static void checkCredentialsUnlessUsingConnection( * a release. Prompted variables are supplied when a release is deployed, so the Octopus CLI * accepts the argument on a create-only command and then ignores it, leaving the deployment to * fail later with "Please provide a variable for the prompted value". + * + *

A step deploys when "Deploy to" is set, and also when the additional arguments carry the + * legacy {@code --deployTo}; only a step that does neither is reported. Which CLI will run is an + * agent-side decision, so this cannot be narrowed further here - on the new CLI {@code + * --deployTo} is not a {@code release create} argument at all and the step fails with {@code + * unknown flag}, which is loud enough to diagnose without help from this check. */ public static void checkPromptedVariablesOnlyWhenDeploying( @NotNull final Map properties, @@ -79,6 +95,9 @@ public static void checkPromptedVariablesOnlyWhenDeploying( if (!VARIABLE_ARGUMENT.matcher(additionalArguments).find()) { return; } + if (DEPLOY_TO_ARGUMENT.matcher(additionalArguments).find()) { + return; + } if (StringUtil.isEmptyOrSpaces(properties.get(constants.getDeployToKey()))) { result.add( new InvalidProperty( diff --git a/octopus-server/src/test/java/octopus/teamcity/server/OctopusCreateReleaseRunTypeValidationTest.java b/octopus-server/src/test/java/octopus/teamcity/server/OctopusCreateReleaseRunTypeValidationTest.java index a943d531..850399d2 100644 --- a/octopus-server/src/test/java/octopus/teamcity/server/OctopusCreateReleaseRunTypeValidationTest.java +++ b/octopus-server/src/test/java/octopus/teamcity/server/OctopusCreateReleaseRunTypeValidationTest.java @@ -86,6 +86,26 @@ void otherArgumentsEndingInVariableAreValid() { .doesNotContain(CONSTANTS.getCommandLineArgumentsKey()); } + @Test + void promptedVariableWithLegacyDeployToArgumentIsValid() { + // The legacy octo CLI takes the deploy target on create-release, so this step does deploy and + // --variable does take effect - verified against octo 9.1.7. + assertThat( + validate(withAdditionalArguments("--deployTo Development --variable ImageTag:1.5.2"))) + .doesNotContain(CONSTANTS.getCommandLineArgumentsKey()); + } + + @Test + void promptedVariableWithLegacyDeployToArgumentInAnyFormIsValid() { + // octo's option parser is case insensitive and accepts -, -- and / prefixes. + for (final String deployTo : + new String[] {"--deployto=Development", "-deployTo Development", "/DEPLOYTO Development"}) { + assertThat(validate(withAdditionalArguments("--variable ImageTag:1.5.2 " + deployTo))) + .as("deploy target supplied as %s", deployTo) + .doesNotContain(CONSTANTS.getCommandLineArgumentsKey()); + } + } + @Test void noAdditionalArgumentsIsValid() { final Map properties = withMandatoryNonCredentialFields(new HashMap<>()); From 27b1c6f5e5a467d84960780bf7374c659baaff3d Mon Sep 17 00:00:00 2001 From: Nick Josevski Date: Thu, 20 Aug 2026 14:45:08 +1000 Subject: [PATCH 4/4] fix: keep -v off the release create command -v is --variable on "release deploy" but --version on "release create". It was missing from deployReleaseAdditionalArgumentsToBeIgnored and only got stripped by accident, as a substring of --variable under the old String.contains matching. Exact Set matching drops that coverage, so -v reached release create and the CLI rejected the release number: The release number 'ImageTag:1.5.2' does not appear to be a valid version number. Verified against a live Octopus by generating the commands from CommandHelper itself: with -v stripped, release create succeeds and the deploy command keeps -v ImageTag:1.5.2, and the prompted variable reaches the script. The release number has its own field, so losing -v as a create-side alias for --version is the cheaper side of the ambiguity. Co-Authored-By: Claude Opus 5 (1M context) --- .../teamcity/agent/cli/CommandHelper.java | 5 +++++ .../teamcity/agent/cli/CommandHelperTest.java | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/octopus-agent/src/main/java/octopus/teamcity/agent/cli/CommandHelper.java b/octopus-agent/src/main/java/octopus/teamcity/agent/cli/CommandHelper.java index 40fe9092..92cc84e0 100644 --- a/octopus-agent/src/main/java/octopus/teamcity/agent/cli/CommandHelper.java +++ b/octopus-agent/src/main/java/octopus/teamcity/agent/cli/CommandHelper.java @@ -33,6 +33,11 @@ public class CommandHelper { "--deploy-at", "--deploy-at-expiry", "--variable", + // -v is --variable on "release deploy" but --version on "release create", so it has to be + // stripped from the create command or a prompted variable is silently used as the release + // number. The release number has its own field, so losing -v as a create-side alias for + // --version is the cheaper side of the ambiguity. + "-v", "--update-variables", "--skip", "--guided-failure", diff --git a/octopus-agent/src/test/java/octopus/teamcity/agent/cli/CommandHelperTest.java b/octopus-agent/src/test/java/octopus/teamcity/agent/cli/CommandHelperTest.java index 0ac975c6..c7817bd9 100644 --- a/octopus-agent/src/test/java/octopus/teamcity/agent/cli/CommandHelperTest.java +++ b/octopus-agent/src/test/java/octopus/teamcity/agent/cli/CommandHelperTest.java @@ -437,6 +437,23 @@ void sanitizeCommandExtraArgsRemovesForbiddenArgWithAnInlineValue() { assertThat(result).containsExactly("--channel", "Beta"); } + @Test + void createReleaseWithDeployToForwardsTheShortPromptedVariableFormToTheDeployCommand() { + // -v is --variable on "release deploy" but --version on "release create". Left on the create + // command the CLI rejects the release number: "The release number 'ImageTag:1.5.2' does not + // appear to be a valid version number". + Map params = new HashMap<>(); + final OctopusConstants constants = OctopusConstants.Instance; + params.put(constants.getProjectNameKey(), "MyProject"); + params.put(constants.getDeployToKey(), "Dev"); + params.put(constants.getCommandLineArgumentsKey(), "-v ImageTag:1.5.2"); + + assertThat(CommandHelper.createRelease(params).buildCommand()) + .doesNotContain("-v", "ImageTag:1.5.2"); + assertThat(CommandHelper.deployRelease(params, "1.0.0")) + .containsSequence("-v", "ImageTag:1.5.2"); + } + @Test void createReleaseWithDeployToForwardsPromptedVariablesToTheDeployCommand() { Map params = new HashMap<>();