Skip to content

Commit

Permalink
Merge pull request #501 from rundeck/fix/exit-code
Browse files Browse the repository at this point in the history
RUN-1634: Fix exit codes on command failures
  • Loading branch information
gschueler authored Apr 20, 2023
2 parents 196b5f1 + eb5fc9c commit aa8adc4
Show file tree
Hide file tree
Showing 34 changed files with 1,164 additions and 116 deletions.
10 changes: 5 additions & 5 deletions rd-cli-acl/src/main/java/org/rundeck/client/ext/acl/Acl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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")
Expand All @@ -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<Map<String, String>> resources(final Map<String, String> resourceMap) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> {
public class InstallPlugin implements RdCommandExtension, RdOutput, Callable<Integer> {
@Setter
private RdTool rdTool;

Expand All @@ -40,7 +40,7 @@ public class InstallPlugin implements RdCommandExtension, RdOutput, Callable<Boo
@CommandLine.Option(names = {"--version", "-v"}, description = "(Optional) Specific version of the plugin you want to install")
String version;

public Boolean call() throws InputError, IOException {
public Integer call() throws InputError, IOException {
RepositoryResponseHandler.handle(
rdTool.apiWithErrorResponse(api -> {
if (version != null) {
Expand All @@ -49,7 +49,7 @@ public Boolean call() throws InputError, IOException {
return api.installPlugin(repoName, pluginId);
}
}), rdOutput);
return true;
return 0;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean>, RdCommandExtension, RdOutput {
public class UninstallPlugin implements Callable<Integer>, RdCommandExtension, RdOutput {
@Setter
private RdTool rdTool;
@Setter
Expand All @@ -38,10 +38,10 @@ public class UninstallPlugin implements Callable<Boolean>, 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> {
public class UploadPlugin extends BaseCommand implements Callable<Integer> {

@CommandLine.Option(names = {"-r", "--repository"}, description = "Target name of repository to upload plugin into.", required = true)
String repoName;
Expand All @@ -35,13 +35,13 @@ public class UploadPlugin extends BaseCommand implements Callable<Boolean> {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
2 changes: 1 addition & 1 deletion rd-cli-enterprise/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
}


Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> {
public class Adhoc extends BaseCommand implements Callable<Integer> {

@CommandLine.Mixin
AdhocBaseOptions options;
Expand All @@ -51,7 +51,7 @@ public class Adhoc extends BaseCommand implements Callable<Boolean> {
@CommandLine.Mixin
NodeFilterBaseOptions nodeFilterOptions;

public Boolean call() throws IOException, InputError {
public Integer call() throws IOException, InputError {
AdhocResponse adhocResponse;

String project = getRdTool().projectOrEnv(options);
Expand Down Expand Up @@ -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;
}

}
Loading

0 comments on commit aa8adc4

Please sign in to comment.