diff --git a/rd-cli-acl/src/main/java/org/rundeck/client/ext/acl/Acl.java b/rd-cli-acl/src/main/java/org/rundeck/client/ext/acl/Acl.java index 1823c484..6f9d987b 100644 --- a/rd-cli-acl/src/main/java/org/rundeck/client/ext/acl/Acl.java +++ b/rd-cli-acl/src/main/java/org/rundeck/client/ext/acl/Acl.java @@ -477,9 +477,9 @@ static class TestOptions } @CommandLine.Command(description = "Test ACL Policies") - public boolean test(@CommandLine.Mixin TestOptions opts) { + public int test(@CommandLine.Mixin TestOptions opts) { if (!applyArgValidate(opts)) { - return false; + return 2; } final RuleEvaluator authorization = createAuthorization(opts); AuthRequest authRequest = createAuthRequestFromArgs(opts); @@ -562,7 +562,7 @@ public boolean test(@CommandLine.Mixin TestOptions opts) { ); } log("The test " + (testPassed ? "passed" : "failed")); - return testPassed; + return testPassed?0:2; } @CommandLine.Command(description = "Create ACL Policies") @@ -580,11 +580,11 @@ public void create(@CommandLine.Mixin AclCreateOptions opts) throws IOException } @CommandLine.Command(description = "Validate ACL Policies") - public boolean validate(@CommandLine.Mixin AclOptions opts) { + public int validate(@CommandLine.Mixin AclOptions opts) { final Validation validation = validatePolicies(opts); reportValidation(validation); log("The validation " + (validation.isValid() ? "passed" : "failed")); - return validation.isValid(); + return validation.isValid() ? 0 : 2; } private HashSet> resources(final Map resourceMap) { diff --git a/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/InstallPlugin.java b/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/InstallPlugin.java index c9f4e3e1..62b57c36 100644 --- a/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/InstallPlugin.java +++ b/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/InstallPlugin.java @@ -27,7 +27,7 @@ import java.util.concurrent.Callable; @CommandLine.Command(description = "Install a plugin from your plugin repository into your Rundeck instance", name = "install") -public class InstallPlugin implements RdCommandExtension, RdOutput, Callable { +public class InstallPlugin implements RdCommandExtension, RdOutput, Callable { @Setter private RdTool rdTool; @@ -40,7 +40,7 @@ public class InstallPlugin implements RdCommandExtension, RdOutput, Callable { if (version != null) { @@ -49,7 +49,7 @@ public Boolean call() throws InputError, IOException { return api.installPlugin(repoName, pluginId); } }), rdOutput); - return true; + return 0; } } diff --git a/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/UninstallPlugin.java b/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/UninstallPlugin.java index 94fcc7d6..b275d571 100644 --- a/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/UninstallPlugin.java +++ b/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/UninstallPlugin.java @@ -27,7 +27,7 @@ import java.util.concurrent.Callable; @CommandLine.Command(description = "Unistall a Rundeck plugin from your Rundeck instance", name = "uninstall") -public class UninstallPlugin implements Callable, RdCommandExtension, RdOutput { +public class UninstallPlugin implements Callable, RdCommandExtension, RdOutput { @Setter private RdTool rdTool; @Setter @@ -38,10 +38,10 @@ public class UninstallPlugin implements Callable, RdCommandExtension, R String pluginId; - public Boolean call() throws InputError, IOException { + public Integer call() throws InputError, IOException { RepositoryResponseHandler.handle( rdTool.apiWithErrorResponse(api -> api.uninstallPlugin(pluginId)), rdOutput ); - return true; + return 0; } } diff --git a/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/UploadPlugin.java b/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/UploadPlugin.java index 243eb5cd..eb041392 100644 --- a/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/UploadPlugin.java +++ b/rd-cli-base/src/main/java/org/rundeck/client/tool/commands/repository/UploadPlugin.java @@ -26,7 +26,7 @@ import java.util.concurrent.Callable; @CommandLine.Command(description = "Upload a Rundeck plugin to your plugin repository", name = "upload") -public class UploadPlugin extends BaseCommand implements Callable { +public class UploadPlugin extends BaseCommand implements Callable { @CommandLine.Option(names = {"-r", "--repository"}, description = "Target name of repository to upload plugin into.", required = true) String repoName; @@ -35,13 +35,13 @@ public class UploadPlugin extends BaseCommand implements Callable { String binaryPath; - public Boolean call() throws InputError, IOException { + public Integer call() throws InputError, IOException { File binary = new File(binaryPath); if (!binary.exists()) throw new IOException(String.format("Unable to find specified file: %s", binaryPath)); RequestBody fileUpload = RequestBody.create(binary, Client.MEDIA_TYPE_OCTET_STREAM); RepositoryResponseHandler.handle(getRdTool().apiWithErrorResponse(api -> api.uploadPlugin(repoName, fileUpload)), getRdOutput()); - return true; + return 0; } } diff --git a/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/InstallPluginTest.groovy b/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/InstallPluginTest.groovy index 56717244..e11a41cf 100644 --- a/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/InstallPluginTest.groovy +++ b/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/InstallPluginTest.groovy @@ -46,10 +46,11 @@ class InstallPluginTest extends Specification { installCmd.pluginId = 'bcf8885df1e8' when: - installCmd.call() + def result = installCmd.call() then: 1 * api.installPlugin("private", 'bcf8885df1e8') >> Calls.response(new ArtifactActionMessage(msg: "Plugin Installed")) 1 * out.output('Plugin Installed') + result == 0 } } diff --git a/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/UninstallPluginTest.groovy b/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/UninstallPluginTest.groovy index 577f8024..e5dabc8d 100644 --- a/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/UninstallPluginTest.groovy +++ b/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/UninstallPluginTest.groovy @@ -43,10 +43,11 @@ class UninstallPluginTest extends Specification { when: - uninstallCmd.call() + def result=uninstallCmd.call() then: 1 * api.uninstallPlugin(_) >> Calls.response(new ArtifactActionMessage(msg: "Plugin Uninstalled")) 1 * out.output('Plugin Uninstalled') + result==0 } } diff --git a/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/UploadPluginTest.groovy b/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/UploadPluginTest.groovy index 7a5c1fe8..0c1ddfd7 100644 --- a/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/UploadPluginTest.groovy +++ b/rd-cli-base/src/test/groovy/org/rundeck/client/tool/commands/repository/UploadPluginTest.groovy @@ -47,10 +47,11 @@ class UploadPluginTest extends Specification { when: - uploadCmd.call() + def result = uploadCmd.call() then: 1 * api.uploadPlugin("private", _) >> Calls.response(new ArtifactActionMessage(msg: "Upload succeeded")) 1 * out.output('Upload succeeded') + result == 0 } } diff --git a/rd-cli-enterprise/build.gradle b/rd-cli-enterprise/build.gradle index 299135c1..a8b40793 100644 --- a/rd-cli-enterprise/build.gradle +++ b/rd-cli-enterprise/build.gradle @@ -14,7 +14,7 @@ dependencies { implementation libs.picocli annotationProcessor libs.picocliCodegen - testImplementation project(":rd-cli-lib"), project(":rd-api-client") + testImplementation project(":rd-cli-lib"), project(":rd-api-client"), project(":rd-testing") testImplementation libs.retrofitMock testImplementation libs.okhttpMockwebserver diff --git a/rd-cli-enterprise/src/main/java/org/rundeck/client/tool/commands/enterprise/cluster/Mode.java b/rd-cli-enterprise/src/main/java/org/rundeck/client/tool/commands/enterprise/cluster/Mode.java index bcf9ef93..a8d6d330 100644 --- a/rd-cli-enterprise/src/main/java/org/rundeck/client/tool/commands/enterprise/cluster/Mode.java +++ b/rd-cli-enterprise/src/main/java/org/rundeck/client/tool/commands/enterprise/cluster/Mode.java @@ -28,14 +28,14 @@ static class Options { } @CommandLine.Command(description = "Set cluster member execution mode Active") - public boolean active(@CommandLine.Mixin Options options) throws IOException, InputError { - return changeMode(ExecutionMode.active, options, EnterpriseApi::executionModeEnable); + public int active(@CommandLine.Mixin Options options) throws IOException, InputError { + return changeMode(ExecutionMode.active, options, EnterpriseApi::executionModeEnable) ? 0 : 1; } @CommandLine.Command(description = "Set cluster member execution mode Passive") - public boolean passive(@CommandLine.Mixin Options options) throws IOException, InputError { - return changeMode(ExecutionMode.passive, options, EnterpriseApi::executionModeDisable); + public int passive(@CommandLine.Mixin Options options) throws IOException, InputError { + return changeMode(ExecutionMode.passive, options, EnterpriseApi::executionModeDisable) ? 0 : 1; } boolean changeMode( diff --git a/rd-cli-enterprise/src/main/java/org/rundeck/client/tool/commands/enterprise/license/License.java b/rd-cli-enterprise/src/main/java/org/rundeck/client/tool/commands/enterprise/license/License.java index feeb7b69..fc3e73d6 100644 --- a/rd-cli-enterprise/src/main/java/org/rundeck/client/tool/commands/enterprise/license/License.java +++ b/rd-cli-enterprise/src/main/java/org/rundeck/client/tool/commands/enterprise/license/License.java @@ -50,7 +50,7 @@ static class StatusOpts { } @CommandLine.Command(description = "license status") - public boolean status(@CommandLine.Mixin StatusOpts opts) throws InputError, IOException { + public int status(@CommandLine.Mixin StatusOpts opts) throws InputError, IOException { RdTool.apiVersionCheck("license status", 41, getClient().getApiVersion()); LicenseResponse response = getClient().apiCall(EnterpriseApi::verifyLicense); @@ -85,9 +85,9 @@ public boolean status(@CommandLine.Mixin StatusOpts opts) throws InputError, IOE opts.getRemaining() )); } - return false; + return 1; } - return !opts.isStatus() || response.isActive(); + return (!opts.isStatus() || response.isActive()) ? 0 : 1; } diff --git a/rd-cli-enterprise/src/test/groovy/org/rundeck/client/tool/commands/enterprise/cluster/ModeSpec.groovy b/rd-cli-enterprise/src/test/groovy/org/rundeck/client/tool/commands/enterprise/cluster/ModeSpec.groovy new file mode 100644 index 00000000..29c53764 --- /dev/null +++ b/rd-cli-enterprise/src/test/groovy/org/rundeck/client/tool/commands/enterprise/cluster/ModeSpec.groovy @@ -0,0 +1,77 @@ +package org.rundeck.client.tool.commands.enterprise.cluster + +import groovy.transform.CompileStatic +import org.rundeck.client.api.model.ExecutionMode +import org.rundeck.client.testing.MockRdTool +import org.rundeck.client.tool.CommandOutput +import org.rundeck.client.tool.RdApp +import org.rundeck.client.tool.commands.enterprise.api.EnterpriseApi +import org.rundeck.client.tool.commands.enterprise.api.model.EnterpriseModeResponse +import org.rundeck.client.tool.commands.enterprise.api.model.LicenseResponse +import org.rundeck.client.tool.commands.enterprise.license.License +import org.rundeck.client.tool.extension.RdTool +import org.rundeck.client.util.Client +import org.rundeck.client.util.RdClientConfig +import retrofit2.Retrofit +import retrofit2.mock.Calls +import spock.lang.Specification + +class ModeSpec extends Specification { + private RdTool setupMock(EnterpriseApi api, CommandOutput out, int apiVersion) { + def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() + def client = new Client(api, retrofit, null, null, apiVersion, true, null) + def rdapp = Mock(RdApp) { + getClient(EnterpriseApi) >> client + getAppConfig() >> Mock(RdClientConfig) + getOutput() >> out + } + def rdTool = new MockRdTool(client: client, rdApp: rdapp) + rdTool.appConfig = Mock(RdClientConfig) + rdTool + } + + def "active"() { + def api = Mock(EnterpriseApi) + def out = Mock(CommandOutput) + RdTool rdTool = setupMock(api, out, 41) + Mode command = new Mode() + command.rdTool = rdTool + def opts = new Mode.Options() + opts.uuid = 'uuid' + + + when: + def result = command.active(opts) + + then: + 1 * api.executionModeEnable('uuid') >> Calls.response(new EnterpriseModeResponse(executionMode: respstatus as ExecutionMode)) + 0 * api._(*_) + result == expected + where: + respstatus | expected + 'active' | 0 + 'passive' | 1 + } + def "passive"() { + def api = Mock(EnterpriseApi) + def out = Mock(CommandOutput) + RdTool rdTool = setupMock(api, out, 41) + Mode command = new Mode() + command.rdTool = rdTool + def opts = new Mode.Options() + opts.uuid = 'uuid' + + + when: + def result = command.passive(opts) + + then: + 1 * api.executionModeDisable('uuid') >> Calls.response(new EnterpriseModeResponse(executionMode: respstatus as ExecutionMode)) + 0 * api._(*_) + result == expected + where: + respstatus | expected + 'active' | 1 + 'passive' | 0 + } +} diff --git a/rd-cli-enterprise/src/test/groovy/org/rundeck/client/tool/commands/enterprise/license/LicenseSpec.groovy b/rd-cli-enterprise/src/test/groovy/org/rundeck/client/tool/commands/enterprise/license/LicenseSpec.groovy new file mode 100644 index 00000000..23589401 --- /dev/null +++ b/rd-cli-enterprise/src/test/groovy/org/rundeck/client/tool/commands/enterprise/license/LicenseSpec.groovy @@ -0,0 +1,97 @@ +package org.rundeck.client.tool.commands.enterprise.license + +import groovy.transform.CompileStatic +import okhttp3.ResponseBody +import org.rundeck.client.api.RundeckApi +import org.rundeck.client.api.model.KeyStorageItem +import org.rundeck.client.testing.MockRdTool +import org.rundeck.client.tool.CommandOutput +import org.rundeck.client.tool.RdApp +import org.rundeck.client.tool.commands.enterprise.api.EnterpriseApi +import org.rundeck.client.tool.commands.enterprise.api.model.LicenseResponse +import org.rundeck.client.tool.extension.RdTool +import org.rundeck.client.util.Client +import org.rundeck.client.util.RdClientConfig +import retrofit2.Retrofit +import retrofit2.mock.Calls +import spock.lang.Specification + +class LicenseSpec extends Specification { + private RdTool setupMock(EnterpriseApi api, CommandOutput out, int apiVersion) { + def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() + def client = new Client(api, retrofit, null, null, apiVersion, true, null) + def rdapp = Mock(RdApp) { + getClient(EnterpriseApi) >> client + getAppConfig() >> Mock(RdClientConfig) + getOutput() >> out + } + def rdTool = new MockRdTool(client: client, rdApp: rdapp) + rdTool.appConfig = Mock(RdClientConfig) + rdTool + } + + def "status"() { + def api = Mock(EnterpriseApi) + def out = Mock(CommandOutput) + RdTool rdTool = setupMock(api, out, 41) + License command = new License() + command.rdTool = rdTool + def opts = new License.StatusOpts() + + + when: + def result = command.status(opts) + + then: + 1 * api.verifyLicense() >> Calls.response(new LicenseResponse()) + 0 * api._(*_) + result == 0 + } + + def "status remaining"() { + def api = Mock(EnterpriseApi) + def out = Mock(CommandOutput) + RdTool rdTool = setupMock(api, out, 41) + License command = new License() + command.rdTool = rdTool + def opts = new License.StatusOpts() + opts.remaining = optremain + + + when: + def result = command.status(opts) + + then: + 1 * api.verifyLicense() >> Calls.response(new LicenseResponse(remaining: remaining)) + 0 * api._(*_) + result == expect + where: + optremain | remaining | expect + 10 | 10 | 0 + 10 | 9 | 1 + 10 | 0 | 1 + } + + def "status expired"() { + def api = Mock(EnterpriseApi) + def out = Mock(CommandOutput) + RdTool rdTool = setupMock(api, out, 41) + License command = new License() + command.rdTool = rdTool + def opts = new License.StatusOpts() + opts.status = true + + + when: + def result = command.status(opts) + + then: + 1 * api.verifyLicense() >> Calls.response(new LicenseResponse(active: active)) + 0 * api._(*_) + result == expect + where: + active | expect + true | 0 + false | 1 + } +} diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Adhoc.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Adhoc.java index fd016f53..1c033318 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Adhoc.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Adhoc.java @@ -40,7 +40,7 @@ */ @CommandLine.Command(description = "Run adhoc command or script on matching nodes.", name = "adhoc", showEndOfOptionsDelimiterInUsageHelp = true) -public class Adhoc extends BaseCommand implements Callable { +public class Adhoc extends BaseCommand implements Callable { @CommandLine.Mixin AdhocBaseOptions options; @@ -51,7 +51,7 @@ public class Adhoc extends BaseCommand implements Callable { @CommandLine.Mixin NodeFilterBaseOptions nodeFilterOptions; - public Boolean call() throws IOException, InputError { + public Integer call() throws IOException, InputError { AdhocResponse adhocResponse; String project = getRdTool().projectOrEnv(options); @@ -135,7 +135,7 @@ public Boolean call() throws IOException, InputError { ); } - return Executions.maybeFollow(getRdTool(), followOptions, outputFormatOption, adhocResponse.execution.getId(), getRdOutput()); + return Executions.maybeFollow(getRdTool(), followOptions, outputFormatOption, adhocResponse.execution.getId(), getRdOutput()) ? 0 : 1; } } diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Executions.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Executions.java index 98f01e3f..7d1f6dc9 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Executions.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Executions.java @@ -56,7 +56,7 @@ static class KillOptions extends ExecutionIdOption{ } @CommandLine.Command(description = "Attempt to kill an execution by ID.") - public boolean kill(@CommandLine.Mixin KillOptions options) throws IOException, InputError { + public int kill(@CommandLine.Mixin KillOptions options) throws IOException, InputError { if (null == options.getId()) { throw new InputError("-e is required"); } @@ -74,7 +74,7 @@ public boolean kill(@CommandLine.Mixin KillOptions options) throws IOException, if (failed) { getRdOutput().warning(String.format("Kill request failed: %s", abort.reason)); } - return !failed; + return !failed ? 0 : 1; } @@ -87,7 +87,7 @@ public void delete(@CommandLine.Mixin ExecutionIdOption options) throws IOExcept @CommandLine.Command(description = "Follow the output of an execution. Restart from the beginning, or begin tailing as it " + "runs.") - public boolean follow(@CommandLine.Mixin ExecutionsFollowOptions options) throws IOException, InputError { + public int follow(@CommandLine.Mixin ExecutionsFollowOptions options) throws IOException, InputError { int max = 500; @@ -111,7 +111,7 @@ public boolean follow(@CommandLine.Mixin ExecutionsFollowOptions options) throws getRdOutput(), options.isOutputFormat() ? Format.formatter(options.getOutputFormat(), ExecLog::toMap, "%", "") : null, waitUnlessInterrupt(2000) - ); + ) ? 0 : 1; } @@ -500,15 +500,21 @@ static class DeleteAllExecCmd { } @CommandLine.Command(description = "Delete all executions for a job.") - public boolean deleteall(@CommandLine.Mixin DeleteAllExecCmd options) throws IOException, InputError { + public int deleteall(@CommandLine.Mixin DeleteAllExecCmd options) throws IOException, InputError { if (!options.isConfirm()) { //request confirmation - String s = System.console().readLine("Really delete all executions for job %s? (y/N) ", options.getId()); + if (!isInteractiveAvailable()) { + getRdOutput().error("No user interaction available. Use --confirm to confirm purge without user interaction"); + getRdOutput().warning("Not deleting executions"); + return 2; + } + + String s = readInteractive("Really delete all executions for job %s? (y/N) ", options.getId()); if (!"y".equals(s)) { getRdOutput().warning("Not deleting executions."); - return false; + return 2; } } @@ -522,7 +528,36 @@ public boolean deleteall(@CommandLine.Mixin DeleteAllExecCmd options) throws IOE } else { getRdOutput().info(String.format("Deleted %d executions.", result.getSuccessCount())); } - return result.isAllsuccessful(); + return result.isAllsuccessful() ? 0 : 1; + } + + static interface Interactive { + boolean isEnabled(); + + String readInteractive(String fmt, Object... args); + } + + static class ConsoleInteractive implements Interactive { + @Override + public boolean isEnabled() { + return System.console() != null; + } + + @Override + public String readInteractive(String fmt, Object... args) { + return System.console().readLine(fmt, args); + } + } + + Interactive interactive = new ConsoleInteractive(); + + private String readInteractive(String fmt, Object... args) { + + return interactive.readInteractive(fmt, args); + } + + private boolean isInteractiveAvailable() { + return interactive.isEnabled(); } @@ -584,7 +619,7 @@ public int deletebulk(@CommandLine.Mixin BulkDeleteCmd options, } else { getRdOutput().warning("No executions found to delete"); } - return options.isRequire()?2:0; + return options.isRequire() ? 2 : 0; } } @@ -594,7 +629,7 @@ public int deletebulk(@CommandLine.Mixin BulkDeleteCmd options, if (!"y".equals(s)) { getRdOutput().warning("Not deleting executions."); - return 1; + return 2; } } final List finalExecIds = execIds; diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Jobs.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Jobs.java index 212c9d80..9d1dcacb 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Jobs.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Jobs.java @@ -90,7 +90,7 @@ boolean isMax() { "before deleting from the server. " + "--idlist/-i, or --job/-j or --group/-g or --jobxact/-J or --groupxact/-G Options are " + "required.") - public boolean purge(@CommandLine.Mixin Purge options, + public int purge(@CommandLine.Mixin Purge options, @CommandLine.Mixin JobOutputFormatOption jobOutputFormatOption, @CommandLine.Mixin JobFileOptions jobFileOptions, @CommandLine.Mixin JobListOptions jobListOptions) throws IOException, InputError { @@ -128,13 +128,13 @@ public boolean purge(@CommandLine.Mixin Purge options, if (null == System.console()) { getRdOutput().error("No user interaction available. Use --confirm to confirm purge without user interaction"); getRdOutput().warning(String.format("Not deleting %d jobs", idsToDelete)); - return false; + return 2; } String s = System.console().readLine("Really delete %d Jobs? (y/N) ", idsToDelete); if (!"y".equals(s)) { getRdOutput().warning(String.format("Not deleting %d jobs", idsToDelete)); - return false; + return 2; } } int batch = options.isBatchSize() ? Math.min(idsToDelete, options.getBatchSize()) : idsToDelete; @@ -147,18 +147,18 @@ public boolean purge(@CommandLine.Mixin Purge options, if (!deletedJobs.isAllsuccessful()) { getRdOutput().error(String.format("Failed to delete %d Jobs%n", deletedJobs.getFailed().size())); getRdOutput().output(deletedJobs.getFailed().stream().map(DeleteJob::toBasicString).collect(Collectors.toList())); - return false; + return 1; } total += finalIds.size(); i += batchToUse; } getRdOutput().info(String.format("%d Jobs were deleted%n", total)); - return true; + return 0; } @CommandLine.Command(description = "Load Job definitions from a file in XML or YAML format.") - public boolean load( + public int load( @CommandLine.Mixin JobLoadOptions options, @CommandLine.Mixin JobFileOptions fileOptions, @CommandLine.Mixin ProjectNameOptions projectNameOptions, @@ -192,7 +192,7 @@ public boolean load( printLoadResult(importResult.getSkipped(), "Skipped", getRdOutput(), verboseOption.isVerbose()); printLoadResult(failed, "Failed", getRdOutput(), verboseOption.isVerbose()); - return failed == null || failed.isEmpty(); + return (failed == null || failed.isEmpty()) ? 0 : 1; } private void printLoadResult( @@ -330,24 +330,24 @@ public void forecast( @CommandLine.Command(description = "Enable execution for a job") - public boolean enable(@CommandLine.Mixin JobIdentOptions options) throws IOException, InputError { - return simpleJobApiCall(RundeckApi::jobExecutionEnable, options, "Enabled Job %s"); + public int enable(@CommandLine.Mixin JobIdentOptions options) throws IOException, InputError { + return simpleJobApiCall(RundeckApi::jobExecutionEnable, options, "Enabled Job %s") ? 0 : 1; } @CommandLine.Command(description = "Disable execution for a job") - public boolean disable(@CommandLine.Mixin JobIdentOptions options) throws IOException, InputError { - return simpleJobApiCall(RundeckApi::jobExecutionDisable, options, "Disabled Job %s"); + public int disable(@CommandLine.Mixin JobIdentOptions options) throws IOException, InputError { + return simpleJobApiCall(RundeckApi::jobExecutionDisable, options, "Disabled Job %s") ? 0 : 1; } @CommandLine.Command(description = "Enable schedule for a job") - public boolean reschedule(@CommandLine.Mixin JobIdentOptions options) throws IOException, InputError { - return simpleJobApiCall(RundeckApi::jobScheduleEnable, options, "Enabled Schedule for Job %s"); + public int reschedule(@CommandLine.Mixin JobIdentOptions options) throws IOException, InputError { + return simpleJobApiCall(RundeckApi::jobScheduleEnable, options, "Enabled Schedule for Job %s") ? 0 : 1; } @CommandLine.Command(description = "Disable schedule for a job") - public boolean unschedule(@CommandLine.Mixin JobIdentOptions options) throws IOException, InputError { - return simpleJobApiCall(RundeckApi::jobScheduleDisable, options, "Disabled Schedule for Job %s"); + public int unschedule(@CommandLine.Mixin JobIdentOptions options) throws IOException, InputError { + return simpleJobApiCall(RundeckApi::jobScheduleDisable, options, "Disabled Schedule for Job %s") ? 0 : 1; } private boolean simpleJobApiCall( @@ -428,7 +428,7 @@ private List getJobList(BulkJobActionOptions options) throws InputError, @CommandLine.Command(description = "Enable execution for a set of jobs. " + "--idlist/-i, or --job/-j or --group/-g or --jobxact/-J or --groupxact/-G Options are " + "required.") - public boolean enablebulk(@CommandLine.Mixin BulkJobActionOptions options, @CommandLine.Mixin VerboseOption verboseOption) throws IOException, InputError { + public int enablebulk(@CommandLine.Mixin BulkJobActionOptions options, @CommandLine.Mixin VerboseOption verboseOption) throws IOException, InputError { List ids = getJobList(options); @@ -437,13 +437,13 @@ public boolean enablebulk(@CommandLine.Mixin BulkJobActionOptions options, @Comm if (null == System.console()) { getRdOutput().error("No user interaction available. Use --confirm to confirm request without user interaction"); getRdOutput().warning(String.format("Not enabling %d jobs", ids.size())); - return false; + return 2; } String s = System.console().readLine("Really enable %d Jobs? (y/N) ", ids.size()); if (!"y".equals(s)) { getRdOutput().warning(String.format("Not enabling %d jobs", ids.size())); - return false; + return 2; } } @@ -458,20 +458,20 @@ public boolean enablebulk(@CommandLine.Mixin BulkJobActionOptions options, @Comm .map(BulkToggleJobExecutionResponse.Result::toString) .collect(Collectors.toList())); } - return true; + return 0; } getRdOutput().error(String.format("Failed to enable %d Jobs%n", response.getFailed().size())); getRdOutput().output(response.getFailed().stream() .map(BulkToggleJobExecutionResponse.Result::toString) .collect(Collectors.toList())); - return false; + return 1; } @CommandLine.Command(description = "Disable execution for a set of jobs. " + "--idlist/-i, or --job/-j or --group/-g or --jobxact/-J or --groupxact/-G Options are " + "required.") - public boolean disablebulk(@CommandLine.Mixin BulkJobActionOptions options, @CommandLine.Mixin VerboseOption verboseOption) throws IOException, InputError { + public int disablebulk(@CommandLine.Mixin BulkJobActionOptions options, @CommandLine.Mixin VerboseOption verboseOption) throws IOException, InputError { List ids = getJobList(options); @@ -480,13 +480,13 @@ public boolean disablebulk(@CommandLine.Mixin BulkJobActionOptions options, @Com if (null == System.console()) { getRdOutput().error("No user interaction available. Use --confirm to confirm request without user interaction"); getRdOutput().warning(String.format("Not disabling %d jobs", ids.size())); - return false; + return 2; } String s = System.console().readLine("Really disable %d Jobs? (y/N) ", ids.size()); if (!"y".equals(s)) { getRdOutput().warning(String.format("Not disabling %d jobs", ids.size())); - return false; + return 2; } } @@ -501,20 +501,20 @@ public boolean disablebulk(@CommandLine.Mixin BulkJobActionOptions options, @Com .map(BulkToggleJobExecutionResponse.Result::toString) .collect(Collectors.toList())); } - return true; + return 0; } getRdOutput().error(String.format("Failed to disable %d Jobs%n", response.getFailed().size())); getRdOutput().output(response.getFailed().stream() .map(BulkToggleJobExecutionResponse.Result::toString) .collect(Collectors.toList())); - return false; + return 1; } @CommandLine.Command(description = "Enable schedule for a set of jobs. " + "--idlist/-i, or --job/-j or --group/-g or --jobxact/-J or --groupxact/-G Options are " + "required.") - public boolean reschedulebulk(@CommandLine.Mixin BulkJobActionOptions options, @CommandLine.Mixin VerboseOption verboseOption) throws IOException, InputError { + public int reschedulebulk(@CommandLine.Mixin BulkJobActionOptions options, @CommandLine.Mixin VerboseOption verboseOption) throws IOException, InputError { List ids = getJobList(options); @@ -523,13 +523,13 @@ public boolean reschedulebulk(@CommandLine.Mixin BulkJobActionOptions options, @ if (null == System.console()) { getRdOutput().error("No user interaction available. Use --confirm to confirm request without user interaction"); getRdOutput().warning(String.format("Not rescheduling %d jobs", ids.size())); - return false; + return 2; } String s = System.console().readLine("Really reschedule %d Jobs? (y/N) ", ids.size()); if (!"y".equals(s)) { getRdOutput().warning(String.format("Not rescheduling %d jobs", ids.size())); - return false; + return 2; } } @@ -544,20 +544,20 @@ public boolean reschedulebulk(@CommandLine.Mixin BulkJobActionOptions options, @ .map(BulkToggleJobScheduleResponse.Result::toString) .collect(Collectors.toList())); } - return true; + return 0; } getRdOutput().error(String.format("Failed to reschedule %d Jobs%n", response.getFailed().size())); getRdOutput().output(response.getFailed().stream() .map(BulkToggleJobScheduleResponse.Result::toString) .collect(Collectors.toList())); - return false; + return 1; } @CommandLine.Command(description = "Disable schedule for a set of jobs. " + "--idlist/-i, or --job/-j or --group/-g or --jobxact/-J or --groupxact/-G Options are " + "required.") - public boolean unschedulebulk(@CommandLine.Mixin BulkJobActionOptions options, @CommandLine.Mixin VerboseOption verboseOption) throws IOException, InputError { + public int unschedulebulk(@CommandLine.Mixin BulkJobActionOptions options, @CommandLine.Mixin VerboseOption verboseOption) throws IOException, InputError { List ids = getJobList(options); @@ -566,13 +566,13 @@ public boolean unschedulebulk(@CommandLine.Mixin BulkJobActionOptions options, @ if (null == System.console()) { getRdOutput().error("No user interaction available. Use --confirm to confirm request without user interaction"); getRdOutput().warning(String.format("Not unscheduling %d jobs", ids.size())); - return false; + return 2; } String s = System.console().readLine("Really unschedule %d Jobs? (y/N) ", ids.size()); if (!"y".equals(s)) { getRdOutput().warning(String.format("Not unscheduling %d jobs", ids.size())); - return false; + return 2; } } @@ -587,13 +587,13 @@ public boolean unschedulebulk(@CommandLine.Mixin BulkJobActionOptions options, @ .map(BulkToggleJobScheduleResponse.Result::toString) .collect(Collectors.toList())); } - return true; + return 0; } getRdOutput().error(String.format("Failed to disable %d Jobs%n", response.getFailed().size())); getRdOutput().output(response.getFailed().stream() .map(BulkToggleJobScheduleResponse.Result::toString) .collect(Collectors.toList())); - return false; + return 1; } diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Keys.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Keys.java index 86ce9591..6f68b903 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Keys.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Keys.java @@ -119,7 +119,7 @@ void validateRequired(Opts opts) { @CommandLine.Command(description = "List the keys and directories at a given path, or at the root by default.", aliases = {"ls"}) - public boolean list(@CommandLine.Mixin OptionalPath opts) throws IOException, InputError { + public int list(@CommandLine.Mixin OptionalPath opts) throws IOException, InputError { KeyStorageItem keyStorageItem = apiCall(api -> api.listKeyStorage(opts.getPath().keysPath())); @@ -131,10 +131,10 @@ public boolean list(@CommandLine.Mixin OptionalPath opts) throws IOException, In .sorted() .map(KeyStorageItem::toBasicString) .collect(Collectors.toList())); - return true; + return 0; } else { getRdOutput().error(String.format("Path is not a directory: %s", opts.getPath())); - return false; + return 2; } } @@ -177,13 +177,13 @@ static class GetOpts extends Opts { } @CommandLine.Command(description = "Get the contents of a public key") - public boolean get(@CommandLine.Mixin GetOpts options) throws IOException, InputError { + public int get(@CommandLine.Mixin GetOpts options) throws IOException, InputError { validateRequired(options); KeyStorageItem keyStorageItem = apiCall(api -> api.listKeyStorage(options.getPath().keysPath())); if (keyStorageItem.getType() != KeyStorageItem.KeyItemType.file) { getRdOutput().error(String.format("Requested path (%s) is not a file", options.getPath())); - return false; + return 2; } if (keyStorageItem.getFileType() != KeyStorageItem.KeyFileType.publicKey) { getRdOutput().error(String.format( @@ -191,7 +191,7 @@ public boolean get(@CommandLine.Mixin GetOpts options) throws IOException, Input options.getPath(), keyStorageItem.getFileType() )); - return false; + return 2; } try (ResponseBody body = apiCall(api -> api.getPublicKey(options.getPath().keysPath()))) { if (!ServiceClient.hasAnyMediaType(body.contentType(), Client.MEDIA_TYPE_GPG_KEYS)) { @@ -213,7 +213,7 @@ public boolean get(@CommandLine.Mixin GetOpts options) throws IOException, Input Util.copyStream(inputStream, System.out); } } - return true; + return 0; } diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Metrics.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Metrics.java index 17df0057..813ea932 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Metrics.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Metrics.java @@ -79,7 +79,7 @@ static class HealthCheckOptions { } @CommandLine.Command(description = "Print health check status information.") - public boolean healthcheck(@CommandLine.Mixin HealthCheckOptions options) throws IOException, InputError { + public int healthcheck(@CommandLine.Mixin HealthCheckOptions options) throws IOException, InputError { Map healthCheckStatus = apiCall(RundeckApi::getHealthCheckMetrics); @@ -111,7 +111,7 @@ public boolean healthcheck(@CommandLine.Mixin HealthCheckOptions options) throws getRdOutput().warning("No results found."); } - return !options.isFailOnUnhealthy() || unhealthyList.size() == 0; + return (!options.isFailOnUnhealthy() || unhealthyList.size() == 0) ? 0 : 1; } // rd metrics threads diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Projects.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Projects.java index 37096123..f64ce707 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Projects.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Projects.java @@ -105,7 +105,7 @@ static class ProjectDelete extends ProjectNameOptions { } @CommandLine.Command(description = "Delete a project") - public boolean delete( + public int delete( @CommandLine.Mixin ProjectDelete options, @CommandLine.Mixin ProjectListFormatOptions formatOptions, @CommandLine.Mixin VerboseOption verboseOption @@ -123,12 +123,12 @@ public boolean delete( if (!"y".equals(s)) { getRdOutput().warning(String.format("Not deleting project %s.", project)); - return false; + return 2; } } apiCall(api -> api.deleteProject(project)); getRdOutput().info(String.format("Project was deleted: %s%n", project)); - return true; + return 0; } @CommandLine.Command(description = "Create a project.") diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Retry.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Retry.java index 8407217d..adeea422 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Retry.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/Retry.java @@ -43,7 +43,7 @@ name = "retry", showEndOfOptionsDelimiterInUsageHelp = true ) -public class Retry extends BaseCommand implements Callable { +public class Retry extends BaseCommand implements Callable { @@ -52,12 +52,12 @@ public class Retry extends BaseCommand implements Callable { @CommandLine.Mixin FollowOptions followOptions; - public Boolean call() throws IOException, InputError { + public Integer call() throws IOException, InputError { getRdTool().requireApiVersion("retry", 24); String jobId = Run.getJobIdFromOpts(options, getRdOutput(), getRdTool(), () -> getRdTool().projectOrEnv(options)); String execId = options.getEid(); if (null == jobId) { - return false; + return 2; } Execution execution; @@ -134,6 +134,6 @@ public Boolean call() throws IOException, InputError { String started = "started"; getRdOutput().info(String.format("Execution %s: %s%n", started, execution.toBasicString())); - return Executions.maybeFollow(getRdTool(), followOptions, options, execution.getId(), getRdOutput()); + return Executions.maybeFollow(getRdTool(), followOptions, options, execution.getId(), getRdOutput()) ? 0 : 1; } } diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/jobs/Files.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/jobs/Files.java index e59ec2fa..fc4640e8 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/jobs/Files.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/jobs/Files.java @@ -142,7 +142,7 @@ static class FileUploadOpts { @CommandLine.Command(description = "Upload a file as input for a job option (API v19). Returns a unique key for the uploaded" + " file, which can be used as the option value when running the job.") - public boolean load(@CommandLine.Mixin FileUploadOpts options) throws IOException, InputError { + public int load(@CommandLine.Mixin FileUploadOpts options) throws IOException, InputError { File input = options.getFile(); if (!input.canRead() || !input.isFile()) { throw new InputError(String.format("File is not readable or does not exist: %s", input)); @@ -161,12 +161,12 @@ public boolean load(@CommandLine.Mixin FileUploadOpts options) throws IOExceptio getRdOutput().info("File " + fileName + " uploaded successfully for option " + options.getOption()); getRdOutput().info("File key:"); getRdOutput().output(fileid); - return true; + return 0; } else { getRdOutput().error(String.format("Expected one option result for option %s, but saw: ", options.getOption())); } getRdOutput().output(jobFileUploadResult); - return false; + return 1; } /** diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/Archives.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/Archives.java index 804159fa..2fcfd3a7 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/Archives.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/Archives.java @@ -114,7 +114,7 @@ static class ArchiveImportOpts extends BaseOptions{ } @CommandLine.Command(description = "Import a project archive", name = "import") - public boolean importArchive(@CommandLine.Mixin ArchiveImportOpts opts) throws InputError, IOException { + public int importArchive(@CommandLine.Mixin ArchiveImportOpts opts) throws InputError, IOException { File input = opts.getFile(); if (!input.canRead() || !input.isFile()) { throw new InputError(String.format("File is not readable or does not exist: %s", input)); @@ -173,7 +173,7 @@ public boolean importArchive(@CommandLine.Mixin ArchiveImportOpts opts) throws I getRdOutput().error(status.aclErrors); } - return opts.isStrict() ? !anyerror : status.getResultSuccess(); + return (opts.isStrict() ? !anyerror : status.getResultSuccess()) ? 0 : 1; } @@ -215,7 +215,7 @@ enum Flags { } @CommandLine.Command(description = "Export a project archive") - public boolean export(@CommandLine.Mixin ArchiveExportOpts opts) throws IOException, InputError { + public int export(@CommandLine.Mixin ArchiveExportOpts opts) throws IOException, InputError { if (opts.isIncludeFlags() && opts.isExecutionIds()) { throw new InputError("Cannot use --execids/-e with --include/-i"); } @@ -246,7 +246,7 @@ public boolean export(@CommandLine.Mixin ArchiveExportOpts opts) throws IOExcept apiCall(api -> api.exportProject(project, opts.getExecutionIds())), opts.getFile() ); - return true; + return 0; } getRdOutput().info(String.format("Export Archive for project: %s", project)); if (opts.isExecutionIds()) { @@ -282,7 +282,7 @@ public boolean export(@CommandLine.Mixin ArchiveExportOpts opts) throws IOExcept } catch (InterruptedException e) { return false; } - }); + }) ? 0 : 2; } public static boolean loopStatus( diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/SCM.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/SCM.java index 0bc4eac2..893e32f0 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/SCM.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/SCM.java @@ -181,7 +181,7 @@ private boolean outputScmActionResult(final CommandOutput output, final ScmActio @CommandLine.Command(description = "Get SCM Status for a Project") - public boolean status(@CommandLine.Mixin BaseOpts opts) throws IOException, InputError { + public int status(@CommandLine.Mixin BaseOpts opts) throws IOException, InputError { String project = validate(opts); ScmProjectStatusResult result = apiCall(api -> api.getScmProjectStatus( project, @@ -190,7 +190,7 @@ public boolean status(@CommandLine.Mixin BaseOpts opts) throws IOException, Inpu getRdOutput().output(result.toMap()); - return result.synchState == ScmSynchState.CLEAN; + return (result.synchState == ScmSynchState.CLEAN) ? 0 : 1; } @@ -326,7 +326,7 @@ public static class ActionPerformOptions extends ActionInputsOptions { } @CommandLine.Command(description = "Perform SCM action") - public boolean perform( + public int perform( @CommandLine.Mixin BaseOpts opts, @CommandLine.Mixin ActionPerformOptions options ) throws IOException, InputError { @@ -398,12 +398,12 @@ public boolean perform( "Action " + options.getAction(), getRdTool().getAppConfig().isAnsiEnabled() )) { - return false; + return 2; } //otherwise check other error codes and fail if necessary ScmActionResult result = getRdTool().getClient().checkError(response); - return outputScmActionResult(getRdOutput(), result, "Action " + options.getAction()); + return outputScmActionResult(getRdOutput(), result, "Action " + options.getAction()) ? 0 : 1; } private ScmActionPerform performFromOptions(final ActionPerformOptions options) throws InputError { diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/system/Mode.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/system/Mode.java index ee4e41ea..ca83ff72 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/system/Mode.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/system/Mode.java @@ -63,14 +63,14 @@ public int info(@CommandLine.Mixin ModeInfo opts) throws IOException, InputError @CommandLine.Command(description = "Set execution mode Active") - public boolean active(@CommandLine.Mixin QuietOption opts) throws IOException, InputError { - return changeMode(opts, ExecutionMode.active, RundeckApi::executionModeEnable); + public int active(@CommandLine.Mixin QuietOption opts) throws IOException, InputError { + return changeMode(opts, ExecutionMode.active, RundeckApi::executionModeEnable) ? 0 : 1; } @CommandLine.Command(description = "Set execution mode Passive") - public boolean passive(@CommandLine.Mixin QuietOption opts) throws IOException, InputError { - return changeMode(opts, ExecutionMode.passive, RundeckApi::executionModeDisable); + public int passive(@CommandLine.Mixin QuietOption opts) throws IOException, InputError { + return changeMode(opts, ExecutionMode.passive, RundeckApi::executionModeDisable) ? 0 : 1; } boolean changeMode( diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/AdhocSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/AdhocSpec.groovy index 2b18a968..dedf61b6 100644 --- a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/AdhocSpec.groovy +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/AdhocSpec.groovy @@ -53,7 +53,7 @@ class AdhocSpec extends Specification { it.url = (url ? scriptUrl : null) } when: "we run adhoc script file" - adhoc.call() + def result = adhoc.call() then: "api call has correct values" 1 * api."${url ? 'runUrl' : 'runScript'}"( 'aproject', @@ -68,6 +68,7 @@ class AdhocSpec extends Specification { ) >> Calls.response(new AdhocResponse(message: 'ok', execution: new Execution(id: '123'))) 1 * api.getExecution('123') >> Calls.response(new Execution(id: '123', description: 'asdf')) 0 * api._(*_) + result == 0 cleanup: tempFile.delete() diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/ExecutionsSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/ExecutionsSpec.groovy index e8cceb69..a523631b 100644 --- a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/ExecutionsSpec.groovy +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/ExecutionsSpec.groovy @@ -16,6 +16,8 @@ package org.rundeck.client.tool.commands +import org.rundeck.client.api.model.AbortResult +import org.rundeck.client.api.model.BulkExecutionDeleteResponse import org.rundeck.client.api.model.Execution import org.rundeck.client.api.model.ExecutionList import org.rundeck.client.api.model.JobItem @@ -356,4 +358,78 @@ class ExecutionsSpec extends Specification { true | true false | false } + + def "kill"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Executions command = new Executions() + command.rdTool = rdTool + command.rdOutput = out + + def options = new Executions.KillOptions() + options.id='1' + + + when: + def result = command.kill(options) + + then: + 1 * api.abortExecution('1', false) >> Calls.response( + new AbortResult( + abort: new AbortResult.Reason(status: status, reason: 'success'), + execution: new Execution(id: '1', description: '') + ) + ) + + 0 * api._(*_) + result==expected + where: + status | expected + + 'failed' | 1 + 'success' | 0 + } + + def "deleteall"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Executions command = new Executions() + command.rdTool = rdTool + command.rdOutput = out + + def options = new Executions.DeleteAllExecCmd() + options.id='jobid' + options.confirm = confirm + command.interactive = Mock(Executions.Interactive) { + _ * isEnabled() >> ienabled + _ * readInteractive(*_) >> iread + } + + + when: + def result = command.deleteall(options) + + then: + (doesapi?1:0) * api.deleteAllJobExecutions('jobid') >> Calls.response( + new BulkExecutionDeleteResponse( + allsuccessful:allsuccess, + failures: allsuccess?[]:[new BulkExecutionDeleteResponse.DeleteFailure(id:'jobid',message:'amessage')] + ) + ) + + 0 * api._(*_) + result==expected + where: + confirm | ienabled | iread | doesapi | allsuccess || expected + false | false | 'y' | false | false || 2 + false | true | 'n' | false | false || 2 + false | true | 'y' | true | false || 1 + true | false | null | true | false || 1 + false | true | 'y' | true | true || 0 + true | false | null | true | true || 0 + } } diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy index 07c368b2..cae81019 100644 --- a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy @@ -16,6 +16,9 @@ package org.rundeck.client.tool.commands +import org.rundeck.client.api.model.BulkToggleJobExecutionResponse +import org.rundeck.client.api.model.BulkToggleJobScheduleResponse +import org.rundeck.client.api.model.DeleteJob import org.rundeck.client.api.model.Simple import org.rundeck.client.api.model.scheduler.ForecastJobItem import org.rundeck.client.testing.MockRdTool @@ -33,6 +36,7 @@ import org.rundeck.client.api.model.JobLoadItem import org.rundeck.client.api.model.scheduler.ScheduledJobItem import org.rundeck.client.tool.RdApp import org.rundeck.client.tool.extension.RdTool +import org.rundeck.client.tool.options.BulkJobActionOptions import org.rundeck.client.tool.options.JobFileOptions import org.rundeck.client.tool.options.JobIdentOptions import org.rundeck.client.tool.options.JobListOptions @@ -209,7 +213,7 @@ class JobsSpec extends Specification { Calls.response([new JobItem(id: 'fakeid')]) 1 * api.deleteJobsBulk({ it.ids == ['fakeid'] }) >> Calls.response(new DeleteJobsResult(allsuccessful: true)) 0 * api._(*_) - result + result == 0 where: job | group | jobexact | groupexact @@ -221,6 +225,38 @@ class JobsSpec extends Specification { null | null | null | 'b/c' } @Unroll + def "job purge some failure"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool = rdTool + command.rdOutput = out + + def opts = new Jobs.Purge() + opts.confirm = true + def listOpts = new JobListOptions() + listOpts.setJob(job) + listOpts.setProject 'ProjectName' + + when: + def result = command.purge(opts, new JobOutputFormatOption(), new JobFileOptions(), listOpts) + + then: + 1 * api.listJobs('ProjectName', job, null,null,null) >> + Calls.response([new JobItem(id: 'fakeid')]) + 1 * api.deleteJobsBulk({ it.ids == ['fakeid'] }) >> Calls.response(new DeleteJobsResult(allsuccessful: allsuccess, failed: allsuccess?[]:[new DeleteJob(id:'fakeid')])) + 0 * api._(*_) + result == exit + + where: + job='a' + allsuccess | exit + true | 0 + false | 1 + } + @Unroll def "job purge with with batchsize"() { given: @@ -252,7 +288,7 @@ class JobsSpec extends Specification { Calls.response(new DeleteJobsResult(allsuccessful: true)) } 0 * api._(*_) - result + result == 0 where: job | batch | total | max || expect @@ -345,7 +381,7 @@ class JobsSpec extends Specification { command.rdOutput=out when: - command.load(new JobLoadOptions(), opts,new ProjectNameOptions(project:'ProjectName'),new VerboseOption()) + def result = command.load(new JobLoadOptions(), opts,new ProjectNameOptions(project:'ProjectName'),new VerboseOption()) then: 1 * api.loadJobs('ProjectName', _, 'yaml', _, _) >> @@ -358,6 +394,34 @@ class JobsSpec extends Specification { 1 * out.info('1 Jobs Failed:\n') 1 * out.output(['[id:?] Job Name\n\t:Test Error']) 0 * out._(*_) + result == 1 + + } + def "job load success"() { + given: + def opts = new JobFileOptions() + opts.format= JobFileOptions.Format.yaml + opts.file=tempFile + + + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool=rdTool + command.rdOutput=out + + when: + def result = command.load(new JobLoadOptions(), opts,new ProjectNameOptions(project:'ProjectName'),new VerboseOption()) + + then: + 1 * api.loadJobs('ProjectName', _, 'yaml', _, _) >> + Calls.response(new ImportResult(succeeded: [new JobLoadItem( id:'jobid',name: 'Job Name')], skipped: [], failed: [])) + 0 * api._(*_) + 1 * out.info('1 Jobs Succeeded:\n') + 1 * out.output(['jobid Job Name']) + 0 * out._(*_) + result == 0 } @@ -375,7 +439,7 @@ class JobsSpec extends Specification { def resultItem = new JobLoadItem(error: 'Test Error', name: 'Job Name') when: - command.load(new JobLoadOptions(), opts,new ProjectNameOptions(project:'ProjectName'),new VerboseOption(verbose: true)) + def result = command.load(new JobLoadOptions(), opts,new ProjectNameOptions(project:'ProjectName'),new VerboseOption(verbose: true)) then: 1 * api.loadJobs('ProjectName', _, 'yaml', _, _) >> @@ -384,6 +448,7 @@ class JobsSpec extends Specification { 1 * out.info('1 Jobs Failed:\n') 1 * out.output([resultItem]) 0 * out._(*_) + result == 1 } @@ -409,6 +474,222 @@ class JobsSpec extends Specification { 1 * out.output([date]) } + def "exec enable"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api, 31) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool = rdTool + command.rdOutput = out + def opts = new JobIdentOptions() + opts.id = 'jobid' + + + def date = new Date() + + when: + def result = command.enable(opts) + + then: + 1 * api.jobExecutionEnable('jobid') >> Calls.response(new Simple(success: issuccess)) + 0 * api._(*_) + result == exit + where: + issuccess | exit + true | 0 + false | 1 + } + def "exec disable"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api, 31) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool = rdTool + command.rdOutput = out + def opts = new JobIdentOptions() + opts.id = 'jobid' + + + def date = new Date() + + when: + def result = command.disable(opts) + + then: + 1 * api.jobExecutionDisable('jobid') >> Calls.response(new Simple(success: issuccess)) + 0 * api._(*_) + result == exit + where: + issuccess | exit + true | 0 + false | 1 + } + def "schedule enable"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api, 31) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool = rdTool + command.rdOutput = out + def opts = new JobIdentOptions() + opts.id = 'jobid' + + + def date = new Date() + + when: + def result = command.reschedule(opts) + + then: + 1 * api.jobScheduleEnable('jobid') >> Calls.response(new Simple(success: issuccess)) + 0 * api._(*_) + result == exit + where: + issuccess | exit + true | 0 + false | 1 + } + def "schedule disable"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api, 31) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool = rdTool + command.rdOutput = out + def opts = new JobIdentOptions() + opts.id = 'jobid' + + + def date = new Date() + + when: + def result = command.unschedule(opts) + + then: + 1 * api.jobScheduleDisable('jobid') >> Calls.response(new Simple(success: issuccess)) + 0 * api._(*_) + result == exit + where: + issuccess | exit + true | 0 + false | 1 + } + def "exec enable bulk"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api, 31) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool = rdTool + command.rdOutput = out + def opts = new BulkJobActionOptions() + opts.confirm = true + opts.idlist = ['jobid'] + + + when: + def result = command.enablebulk(opts, new VerboseOption()) + + then: + 1 * api.bulkEnableJobs(_) >> Calls.response(new BulkToggleJobExecutionResponse( + allsuccessful: issuccess, + failed: issuccess?[]:[new BulkToggleJobExecutionResponse.Result()] + )) + 0 * api._(*_) + result == exit + where: + issuccess | exit + true | 0 + false | 1 + } + def "exec disable bulk"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api, 31) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool = rdTool + command.rdOutput = out + def opts = new BulkJobActionOptions() + opts.confirm = true + opts.idlist = ['jobid'] + + + when: + def result = command.disablebulk(opts, new VerboseOption()) + + then: + 1 * api.bulkDisableJobs(_) >> Calls.response(new BulkToggleJobExecutionResponse( + allsuccessful: issuccess, + failed: issuccess?[]:[new BulkToggleJobExecutionResponse.Result()] + )) + 0 * api._(*_) + result == exit + where: + issuccess | exit + true | 0 + false | 1 + } + def "schedule enable bulk"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api, 31) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool = rdTool + command.rdOutput = out + def opts = new BulkJobActionOptions() + opts.confirm = true + opts.idlist = ['jobid'] + + + when: + def result = command.reschedulebulk(opts, new VerboseOption()) + + then: + 1 * api.bulkEnableJobSchedule(_) >> Calls.response(new BulkToggleJobScheduleResponse( + allsuccessful: issuccess, + failed: issuccess?[]:[new BulkToggleJobScheduleResponse.Result()] + )) + 0 * api._(*_) + result == exit + where: + issuccess | exit + true | 0 + false | 1 + } + def "schedule disable bulk"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api, 31) + def out = Mock(CommandOutput) + Jobs command = new Jobs() + command.rdTool = rdTool + command.rdOutput = out + def opts = new BulkJobActionOptions() + opts.confirm = true + opts.idlist = ['jobid'] + + + when: + def result = command.unschedulebulk(opts, new VerboseOption()) + + then: + 1 * api.bulkDisableJobSchedule(_) >> Calls.response(new BulkToggleJobScheduleResponse( + allsuccessful: issuccess, + failed: issuccess?[]:[new BulkToggleJobScheduleResponse.Result()] + )) + 0 * api._(*_) + result == exit + where: + issuccess | exit + true | 0 + false | 1 + } } diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/KeysSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/KeysSpec.groovy index f42cca1e..ff76a801 100644 --- a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/KeysSpec.groovy +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/KeysSpec.groovy @@ -1,5 +1,7 @@ package org.rundeck.client.tool.commands +import okhttp3.MediaType +import okhttp3.ResponseBody import org.rundeck.client.testing.MockRdTool import org.rundeck.client.tool.CommandOutput import org.rundeck.client.tool.InputError @@ -283,15 +285,125 @@ class KeysSpec extends Specification { when: - command.list(opts) + def result = command.list(opts) then: - 1 * api.listKeyStorage(_) >> Calls.response(new KeyStorageItem()) + 1 * api.listKeyStorage(_) >> Calls.response(new KeyStorageItem(type: KeyStorageItem.KeyItemType.directory)) 0 * api._(*_) + result == 0 where: input | _ null | _ 'keys/' | _ } + + @Unroll + def "list path is not a directory"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Keys command = new Keys() + command.rdTool=rdTool + command.rdOutput=out + def opts = new Keys.OptionalPath() + opts.path= new Keys.Path(input?:"") + + + when: + def result = command.list(opts) + + then: + 1 * api.listKeyStorage(_) >> Calls.response(new KeyStorageItem(type: KeyStorageItem.KeyItemType.file)) + 0 * api._(*_) + result == 2 + + where: + input | _ + 'keys/afile' | _ + } + + + @Unroll + def "get public key not file"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Keys command = new Keys() + command.rdTool=rdTool + command.rdOutput=out + def opts = new Keys.GetOpts() + opts.path= new Keys.Path(input?:"") + + + when: + def result = command.get(opts) + + then: + 1 * api.listKeyStorage(_) >> Calls.response(new KeyStorageItem(type: KeyStorageItem.KeyItemType.directory)) + 0 * api._(*_) + result == 2 + + where: + input | _ + 'keys/apath' | _ + } + + @Unroll + def "get public key not public"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Keys command = new Keys() + command.rdTool=rdTool + command.rdOutput=out + def opts = new Keys.GetOpts() + opts.path= new Keys.Path(input?:"") + + + when: + def result = command.get(opts) + + then: + 1 * api.listKeyStorage(_) >> Calls.response(new KeyStorageItem(type: KeyStorageItem.KeyItemType.file, meta: ['Rundeck-key-type':fileType.toString()])) + 0 * api._(*_) + result == 2 + + where: + input | fileType + 'keys/apath' | KeyStorageItem.KeyFileType.privateKey + 'keys/apath' | KeyStorageItem.KeyFileType.password + } + @Unroll + def "get public key"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Keys command = new Keys() + command.rdTool=rdTool + command.rdOutput=out + def opts = new Keys.GetOpts() + opts.path= new Keys.Path(input?:"") + + + when: + def result = command.get(opts) + + then: + 1 * api.listKeyStorage('apath') >> Calls.response(new KeyStorageItem(type: KeyStorageItem.KeyItemType.file, meta: ['Rundeck-key-type':'public'])) + 1 * api.getPublicKey('apath') >> Calls.response(ResponseBody.create( + 'somecontent', + Client.MEDIA_TYPE_GPG_KEYS + )) + 0 * api._(*_) + result == 0 + + where: + input | fileType + 'keys/apath' | KeyStorageItem.KeyFileType.publicKey + } } diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/MetricsSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/MetricsSpec.groovy index af13b1e7..455db9cb 100644 --- a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/MetricsSpec.groovy +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/MetricsSpec.groovy @@ -16,12 +16,23 @@ package org.rundeck.client.tool.commands +import okhttp3.RequestBody import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.RecordedRequest +import okio.Buffer import org.rundeck.client.api.RundeckApi +import org.rundeck.client.api.model.KeyStorageItem +import org.rundeck.client.api.model.metrics.HealthCheckStatus +import org.rundeck.client.testing.MockRdTool +import org.rundeck.client.tool.CommandOutput +import org.rundeck.client.tool.RdApp +import org.rundeck.client.tool.extension.RdTool +import org.rundeck.client.util.Client +import org.rundeck.client.util.RdClientConfig import retrofit2.Retrofit import retrofit2.converter.jackson.JacksonConverterFactory +import retrofit2.mock.Calls import spock.lang.Specification /** @@ -375,4 +386,44 @@ class MetricsSpec extends Specification { server.shutdown() } + def "healthcheck"(){ + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Metrics command = new Metrics() + command.rdTool=rdTool + command.rdOutput=out + + def opts = new Metrics.HealthCheckOptions() + opts.failOnUnhealthy=failonunhealthy + def response = new HashMap() + response.put('test',new HealthCheckStatus(healthy: allhealthy)) + when: + def result=command.healthcheck(opts) + + then: + 1 * api.getHealthCheckMetrics() >> Calls.response(response) + 0 * api._(*_) + result==expect + where: + allhealthy | failonunhealthy | expect + true | false | 0 + false | false | 0 + true | true | 0 + false | true | 1 + } + private RdTool setupMock(RundeckApi api, int apiVersion=18) { + def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() + def client = new Client(api, retrofit, null, null, apiVersion, true, null) + def rdapp = Mock(RdApp) { + getClient() >> client + getAppConfig() >> Mock(RdClientConfig) + } + def rdTool = new MockRdTool(client: client, rdApp: rdapp) + rdTool.appConfig = Mock(RdClientConfig) + rdTool + } + + } diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/ProjectsSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/ProjectsSpec.groovy index f31d507f..eadc16fd 100644 --- a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/ProjectsSpec.groovy +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/ProjectsSpec.groovy @@ -107,4 +107,27 @@ class ProjectsSpec extends Specification { '%name/%config' | ['abc/{xyz=993}', 'def/'] '%name/%config.xyz' | ['abc/993', 'def/'] } + def "projects delete"() { + given: + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Projects command = new Projects() + command.rdTool = rdTool + command.rdOutput = out + + def verbose = new VerboseOption() + def format = new ProjectListFormatOptions() + def opts = new Projects.ProjectDelete() + opts.project='aproject' + opts.confirm = true + + when: + def result=command.delete(opts,format, verbose) + + then: + 1 * api.deleteProject('aproject')>>Calls.response(null) + 0 * api._(*_) + result == 0 + } } diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/RetrySpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/RetrySpec.groovy index dc0ef041..3847ff65 100644 --- a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/RetrySpec.groovy +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/RetrySpec.groovy @@ -90,7 +90,7 @@ class RetrySpec extends Specification { } ) >> Calls.response(new Execution(id: 123, description: '')) 0 * api._(*_) - result + result==0 } def "error on api version below 24"() { given: diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/jobs/FilesSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/jobs/FilesSpec.groovy new file mode 100644 index 00000000..0ac9a0e7 --- /dev/null +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/jobs/FilesSpec.groovy @@ -0,0 +1,77 @@ +package org.rundeck.client.tool.commands.jobs + +import groovy.transform.CompileStatic +import org.rundeck.client.api.RundeckApi +import org.rundeck.client.api.model.ExecutionList +import org.rundeck.client.api.model.JobFileUploadResult +import org.rundeck.client.api.model.Paging +import org.rundeck.client.testing.MockRdTool +import org.rundeck.client.tool.CommandOutput +import org.rundeck.client.tool.RdApp +import org.rundeck.client.tool.commands.Executions +import org.rundeck.client.tool.extension.RdTool +import org.rundeck.client.tool.options.ExecutionOutputFormatOption +import org.rundeck.client.tool.options.PagingResultOptions +import org.rundeck.client.util.Client +import org.rundeck.client.util.RdClientConfig +import retrofit2.Retrofit +import retrofit2.mock.Calls +import spock.lang.Specification + +class FilesSpec extends Specification { + + + def "load"() { + + given: + + File toupload = java.nio.file.Files.createTempFile('test', 'file').toFile() + toupload << 'content' + toupload.deleteOnExit() + String fileName = toupload.getName() + def api = Mock(RundeckApi) + RdTool rdTool = setupMock(api) + def out = Mock(CommandOutput) + Files command = new Files() + command.rdTool = rdTool + command.rdOutput = out + + def options = new Files.FileUploadOpts() + options.with { + it.id = 'jobId' + it.option = 'optionName' + it.file = toupload + } + + when: + def result = command.load(options) + then: + 1 * api.uploadJobOptionFile('jobId', 'optionName', fileName, _) >> Calls.response( + new JobFileUploadResult(total: 1, options: resultopts) + ) + result == expect + + cleanup: + + toupload.delete() + + where: + resultopts | expect + [:] | 1 + [optionName: 'afileid'] | 0 + + } + + private RdTool setupMock(RundeckApi api) { + def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() + def client = new Client(api, retrofit, null, null, 18, true, null) + def rdapp = Mock(RdApp) { + getClient() >> client + getAppConfig() >> Mock(RdClientConfig) + } + def rdTool = new MockRdTool(client: client, rdApp: rdapp) + rdTool.appConfig = Mock(RdClientConfig) + rdTool + } + +} diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/ArchivesSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/ArchivesSpec.groovy index aacf0576..74af462d 100644 --- a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/ArchivesSpec.groovy +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/ArchivesSpec.groovy @@ -74,7 +74,64 @@ class ArchivesSpec extends Specification { 'importOpts.test-comp.key' : 'value', ], _ - ) >> Calls.response(new ProjectImportStatus()) + ) >> Calls.response(new ProjectImportStatus(successful: true)) 0 * api._(*_) + result == 0 + } + + def "import some failure has correct exit code"() { + + def api = Mock(RundeckApi) + + def retrofit = new Retrofit.Builder() + .addConverterFactory(JacksonConverterFactory.create()) + .baseUrl('http://example.com/fake/').build() + def out = Mock(CommandOutput) + def client = new Client(api, retrofit, null, null, 18, true, null) + + def rdapp = Mock(RdApp) { + getClient() >> client + getAppConfig() >> Mock(RdClientConfig) + } + def rdTool = new RdToolImpl(rdapp) + + def sut = new Archives() + sut.rdOutput = out + sut.rdTool = rdTool + def opts = new Archives.ArchiveImportOpts() + opts.file = tempFile + opts.project = 'Aproj' + opts.strict = isstrict + + + when: + def result = sut.importArchive(opts) + + then: + 1 * api.importProjectArchive( + 'Aproj', + _, + _, + _, + _, + _, + _, + _, + _, + [:], + _ + ) >> Calls.response(new ProjectImportStatus(resultsmap)) + 0 * api._(*_) + result == expectExit + where: + resultsmap | isstrict | expectExit + [successful: true] | false | 0 + [successful: true, executionErrors: ['error']] | false | 0 + [successful: true, executionErrors: ['error']] | true | 1 + [successful: true, aclErrors: ['error']] | false | 0 + [successful: true, aclErrors: ['error']] | true | 1 + [successful: false] | false | 1 + [successful: false] | true | 1 + } } diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/SCMSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/SCMSpec.groovy index 31688083..6dc91e27 100644 --- a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/SCMSpec.groovy +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/SCMSpec.groovy @@ -195,6 +195,56 @@ class SCMSpec extends Specification { new ScmActionResult(success: true) ) 0 * api._(*_) + result==0 + + where: + all | modified | deleted | expected | expectedDeleted + true | false | false | ['a'] | ['b'] + false | true | false | ['a'] | [] + false | false | true | [] | ['b'] + } + def "perform action failure"() { + given: + def api = Mock(RundeckApi) + def rdTool = setupMock(api) + def out = rdTool.getRdApp().getOutput() + def scm = new SCM() + scm.rdTool = rdTool + scm.rdOutput = out + + def baseopts = new SCM.BaseOpts() + baseopts.project = 'aproject' + baseopts.integration = 'export' + + + def opts = new SCM.ActionPerformOptions() + opts.action = 'export-all' + opts.allItems = all + opts.allModifiedItems = modified + opts.allDeletedItems = deleted + + def items = [ + new ScmExportItem(itemId: 'a', deleted: false), + new ScmExportItem(itemId: 'b', deleted: true), + ] + when: + def result = scm.perform(baseopts, opts) + then: + + 1 * api.getScmActionInputs('aproject', 'export', 'export-all') >> + Calls.response( + new ScmActionInputsResult(integration: 'export', actionId: 'export-all', exportItems: items) + ) + + 1 * api.performScmAction('aproject', 'export', 'export-all', { ScmActionPerform arg -> + arg.items == expected && arg.deleted == expectedDeleted + } + ) >> + Calls.response( + new ScmActionResult(success: false) + ) + 0 * api._(*_) + result==1 where: all | modified | deleted | expected | expectedDeleted @@ -302,7 +352,7 @@ class SCMSpec extends Specification { def result = scm.status(baseopts) then: - result + result==0 1 * appConfig.require('RD_PROJECT', _) >> 'TestProject' @@ -312,4 +362,33 @@ class SCMSpec extends Specification { ) 1 * out.output([message: 'test', actions: [], synchState: 'CLEAN']) } + def "scm status not clean exit code"() { + given: + def api = Mock(RundeckApi) + def rdTool = setupMock(api) + def out = rdTool.getRdApp().getOutput() + def appConfig = rdTool.appConfig + def scm = new SCM() + scm.rdTool = rdTool + scm.rdOutput = out + + def baseopts = new SCM.BaseOpts() + baseopts.project = null + baseopts.integration = 'import' + + + when: + def result = scm.status(baseopts) + + then: + + 1 * appConfig.require('RD_PROJECT', _) >> 'TestProject' + + 1 * api.getScmProjectStatus('TestProject', 'import') >> + Calls.response( + new ScmProjectStatusResult(actions: [], message: 'test', synchState: ScmSynchState.IMPORT_NEEDED) + ) + + result==1 + } } diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/system/ModeSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/system/ModeSpec.groovy new file mode 100644 index 00000000..78e5d597 --- /dev/null +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/system/ModeSpec.groovy @@ -0,0 +1,79 @@ +package org.rundeck.client.tool.commands.system + +import groovy.transform.CompileStatic +import org.rundeck.client.api.RundeckApi +import org.rundeck.client.api.model.ExecutionMode +import org.rundeck.client.api.model.SystemMode +import org.rundeck.client.testing.MockRdTool +import org.rundeck.client.tool.CommandOutput +import org.rundeck.client.tool.RdApp +import org.rundeck.client.tool.commands.enterprise.api.EnterpriseApi +import org.rundeck.client.tool.commands.enterprise.api.model.EnterpriseModeResponse +import org.rundeck.client.tool.extension.RdTool +import org.rundeck.client.tool.options.QuietOption +import org.rundeck.client.util.Client +import org.rundeck.client.util.RdClientConfig +import retrofit2.Retrofit +import retrofit2.mock.Calls +import spock.lang.Specification + +class ModeSpec extends Specification { + private RdTool setupMock(RundeckApi api, CommandOutput out, int apiVersion) { + def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() + def client = new Client(api, retrofit, null, null, apiVersion, true, null) + def rdapp = Mock(RdApp) { + getClient() >> client + getAppConfig() >> Mock(RdClientConfig) + getOutput() >> out + } + def rdTool = new MockRdTool(client: client, rdApp: rdapp) + rdTool.appConfig = Mock(RdClientConfig) + rdTool + } + + def "passive"() { + def api = Mock(RundeckApi) + def out = Mock(CommandOutput) + RdTool rdTool = setupMock(api, out, 41) + Mode command = new Mode() + command.rdOutput = out + command.rdTool = rdTool + def opts = new QuietOption() + + + when: + def result = command.passive(opts) + + then: + 1 * api.executionModeDisable() >> Calls.response(new SystemMode(executionMode: respstatus as ExecutionMode)) + 0 * api._(*_) + result == expected + where: + respstatus | expected + 'active' | 1 + 'passive' | 0 + } + + def "active"() { + def api = Mock(RundeckApi) + def out = Mock(CommandOutput) + RdTool rdTool = setupMock(api, out, 41) + Mode command = new Mode() + command.rdOutput = out + command.rdTool = rdTool + def opts = new QuietOption() + + + when: + def result = command.active(opts) + + then: + 1 * api.executionModeEnable() >> Calls.response(new SystemMode(executionMode: respstatus as ExecutionMode)) + 0 * api._(*_) + result == expected + where: + respstatus | expected + 'active' | 0 + 'passive' | 1 + } +}