From 5ba29722b8dafef688a3c6ab5e0d4edf78633bd2 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 20 Oct 2017 09:49:59 -0700 Subject: [PATCH] Fix #126 Add API version downgrading RD_API_DOWNGRADE=true will enable automatic downgrading RD_API_VERSION=X will force an api version independent of URL --- build.gradle | 2 +- .../org/rundeck/client/RundeckClient.java | 70 ++++-- .../org/rundeck/client/api/RequestFailed.java | 12 +- .../client/api/model/ErrorResponse.java | 2 +- .../org/rundeck/client/tool/AppConfig.java | 1 + .../java/org/rundeck/client/tool/Main.java | 108 ++++++++- .../java/org/rundeck/client/tool/RdApp.java | 7 + .../client/tool/commands/AppCommand.java | 43 +++- .../java/org/rundeck/client/util/Client.java | 229 +++++++++++++++--- .../tool/commands/ExecutionsSpec.groovy | 2 +- .../client/tool/commands/JobsSpec.groovy | 12 +- .../client/tool/commands/KeysSpec.groovy | 4 +- .../client/tool/commands/ProjectsSpec.groovy | 3 +- .../client/tool/commands/RunSpec.groovy | 6 +- .../client/tool/commands/TokensSpec.groovy | 4 +- .../tool/commands/projects/SCMSpec.groovy | 8 +- 16 files changed, 430 insertions(+), 83 deletions(-) diff --git a/build.gradle b/build.gradle index 85a62e80..a091296c 100644 --- a/build.gradle +++ b/build.gradle @@ -179,7 +179,7 @@ repositories { maven { url "https://jitpack.io" } } -ext.toolbeltVersion = "0.1.19" +ext.toolbeltVersion = "0.1.20" ext.toolbeltGroup = "com${toolbeltVersion.contains('SNAPSHOT')?'':'.github'}.simplifyops.cli-toolbelt" dependencies { diff --git a/src/main/java/org/rundeck/client/RundeckClient.java b/src/main/java/org/rundeck/client/RundeckClient.java index f543146a..4f1ac0d0 100644 --- a/src/main/java/org/rundeck/client/RundeckClient.java +++ b/src/main/java/org/rundeck/client/RundeckClient.java @@ -35,9 +35,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import static org.rundeck.client.tool.Main.ENV_CONNECT_RETRY; -import static org.rundeck.client.tool.Main.ENV_DEBUG; -import static org.rundeck.client.tool.Main.ENV_HTTP_TIMEOUT; +import static org.rundeck.client.tool.Main.*; /** * Build a {@link Client} for {@link RundeckApi} using {@link #builder()}. @@ -55,6 +53,7 @@ public class RundeckClient { private RundeckClient() { } + @SuppressWarnings("UnusedReturnValue") public static class Builder { final OkHttpClient.Builder okhttp; @@ -62,6 +61,9 @@ public static class Builder { String appBaseUrl; int httpLogging; HttpUrl parseUrl; + Integer apiVersion; + boolean allowVersionDowngrade; + Client.Logger logger; Builder() { this.okhttp = new OkHttpClient.Builder(); @@ -73,13 +75,14 @@ Builder accept(BuildWith bw, T i) { } public Builder config(AppConfig config) { - logging(config.getInt(ENV_DEBUG, 0)); + logging(config.getDebugLevel()); retryConnect(config.getBool(ENV_CONNECT_RETRY, true)); timeout(config.getLong(ENV_HTTP_TIMEOUT, null)); bypassUrl(config.getString(ENV_BYPASS_URL, null)); insecureSSL(config.getBool(ENV_INSECURE_SSL, false)); insecureSSLHostname(config.getBool(ENV_INSECURE_SSL_HOSTNAME, false)); alternateSSLHostname(config.getString(ENV_ALT_SSL_HOSTNAME, null)); + allowVersionDowngrade(config.getBool(RD_API_DOWNGRADE, false)); return this; } @@ -115,6 +118,16 @@ public Builder baseUrl(final String baseUrl) { return this; } + public Builder apiVersion(final int version) { + this.apiVersion = version; + return this; + } + + public Builder allowVersionDowngrade(final boolean allow) { + this.allowVersionDowngrade = allow; + return this; + } + public Builder tokenAuth(final String authToken) { buildTokenAuth(okhttp, baseUrl, authToken); return this; @@ -126,7 +139,7 @@ public Builder passwordAuth(final String username, final String password) { } public Client build() { - return buildRundeckClient(okhttp, baseUrl, API_VERS); + return buildRundeckClient(); } public Builder logging(final int p) { @@ -134,6 +147,11 @@ public Builder logging(final int p) { return accept(RundeckClient::configLogging, p); } + public Builder logger(Client.Logger logger) { + this.logger = logger; + return this; + } + private static void buildTokenAuth( final OkHttpClient.Builder builder, final String baseUrl, @@ -196,21 +214,26 @@ private static void buildFormAuth( } - private static Client buildRundeckClient( - final OkHttpClient.Builder builder, - final String baseUrl, - final int apiVers - ) - { - String apiBaseUrl = buildApiUrlForVersion(baseUrl, apiVers); - int usedApiVers = apiVersionForUrl(baseUrl, apiVers); + private Client buildRundeckClient() { + //url without version + String appBaseUrl = buildBaseAppUrlForVersion(baseUrl); + final String apiBaseUrl; + if (null != apiVersion) { + //construct api url using requested version + apiBaseUrl = buildApiUrlForVersion(appBaseUrl, apiVersion); + } else { + //if url has no version, use default + apiBaseUrl = buildApiUrlForVersion(baseUrl, API_VERS); + } + //detected final version + int usedApiVers = apiVersionForUrl(apiBaseUrl, API_VERS); - builder.addInterceptor(new StaticHeaderInterceptor("User-Agent", USER_AGENT)); + okhttp.addInterceptor(new StaticHeaderInterceptor("User-Agent", USER_AGENT)); Retrofit build = new Retrofit.Builder() .baseUrl(apiBaseUrl) - .client(builder.build()) + .client(okhttp.build()) .addConverterFactory(new QualifiedTypeConverterFactory( JacksonConverterFactory.create(), SimpleXmlConverterFactory.create(), @@ -218,7 +241,15 @@ private static Client buildRundeckClient( )) .build(); - return new Client<>(build.create(RundeckApi.class), build, usedApiVers); + return new Client<>( + build.create(RundeckApi.class), + build, + appBaseUrl, + apiBaseUrl, + usedApiVers, + allowVersionDowngrade, + logger + ); } private static void validateNotempty(final String authToken, final String s) { @@ -384,6 +415,13 @@ private static Builder configLogging(final Builder builder, final int httpLoggin } + /** + * Normalize a url by appending a / if not present + * + * @param baseUrl + * + * @return + */ private static String normalizeUrlPath(String baseUrl) { if (!baseUrl.matches(".*/$")) { return baseUrl + "/"; diff --git a/src/main/java/org/rundeck/client/api/RequestFailed.java b/src/main/java/org/rundeck/client/api/RequestFailed.java index 9c94c212..84c19962 100644 --- a/src/main/java/org/rundeck/client/api/RequestFailed.java +++ b/src/main/java/org/rundeck/client/api/RequestFailed.java @@ -20,8 +20,8 @@ * Http request failure */ public class RequestFailed extends RuntimeException { - final int statusCode; - final String status; + private final int statusCode; + private final String status; public RequestFailed(final int statusCode, final String status) { this.statusCode = statusCode; @@ -45,4 +45,12 @@ public RequestFailed(final Throwable cause, final int statusCode, final String s this.statusCode = statusCode; this.status = status; } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } } diff --git a/src/main/java/org/rundeck/client/api/model/ErrorResponse.java b/src/main/java/org/rundeck/client/api/model/ErrorResponse.java index 1083b613..8e38b14f 100644 --- a/src/main/java/org/rundeck/client/api/model/ErrorResponse.java +++ b/src/main/java/org/rundeck/client/api/model/ErrorResponse.java @@ -73,7 +73,7 @@ public int getApiVersion() { @Override public String toString() { return String.format( - "%s%n%s%n", + "%s%n%s", getErrorMessage() != null ? getErrorMessage() : "(no message)", toCodeString() ); diff --git a/src/main/java/org/rundeck/client/tool/AppConfig.java b/src/main/java/org/rundeck/client/tool/AppConfig.java index 0f80bdca..16a5d2d2 100644 --- a/src/main/java/org/rundeck/client/tool/AppConfig.java +++ b/src/main/java/org/rundeck/client/tool/AppConfig.java @@ -24,6 +24,7 @@ */ public interface AppConfig extends ConfigSource { boolean isAnsiEnabled(); + int getDebugLevel(); String getDateFormat(); } diff --git a/src/main/java/org/rundeck/client/tool/Main.java b/src/main/java/org/rundeck/client/tool/Main.java index 13394ca0..17f21809 100644 --- a/src/main/java/org/rundeck/client/tool/Main.java +++ b/src/main/java/org/rundeck/client/tool/Main.java @@ -21,6 +21,7 @@ import com.simplifyops.toolbelt.format.yaml.snakeyaml.YamlFormatter; import com.simplifyops.toolbelt.input.jewelcli.JewelInput; import org.rundeck.client.RundeckClient; +import org.rundeck.client.api.RequestFailed; import org.rundeck.client.api.RundeckApi; import org.rundeck.client.api.model.*; import org.rundeck.client.tool.commands.*; @@ -32,6 +33,8 @@ import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.representer.Representer; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.*; import java.util.function.Function; @@ -47,23 +50,46 @@ public class Main { public static final String ENV_PASSWORD = "RD_PASSWORD"; public static final String ENV_TOKEN = "RD_TOKEN"; public static final String ENV_URL = "RD_URL"; + public static final String ENV_API_VERSION = "RD_API_VERSION"; + /** + * If true, allow API version to be automatically degraded when unsupported version is detected + */ + public static final String RD_API_DOWNGRADE = "RD_API_DOWNGRADE"; public static final String ENV_AUTH_PROMPT = "RD_AUTH_PROMPT"; public static final String ENV_DEBUG = "RD_DEBUG"; public static final String ENV_HTTP_TIMEOUT = "RD_HTTP_TIMEOUT"; public static final String ENV_CONNECT_RETRY = "RD_CONNECT_RETRY"; + public static final String ENV_RD_FORMAT = "RD_FORMAT"; public static void main(String[] args) throws CommandRunFailure { - tool(new Rd(new Env())).runMain(args, true); + Rd rd = new Rd(new Env()); + Tool tool = tool(rd); + boolean success = false; + try { + success = tool.runMain(args, false); + } catch (RequestFailed failure) { + rd.getOutput().error(failure.getMessage()); + if (rd.getDebugLevel() > 0) { + StringWriter sb = new StringWriter(); + failure.printStackTrace(new PrintWriter(sb)); + rd.getOutput().error(sb.toString()); + } + } + if (!success) { + System.exit(2); + } } - private static void setupFormat(final ToolBelt belt, AppConfig config) { - final String format = config.get("RD_FORMAT"); + final String format = config.get(ENV_RD_FORMAT); if ("yaml".equalsIgnoreCase(format)) { configYamlFormat(belt, config); } else if ("json".equalsIgnoreCase(format)) { configJsonFormat(belt); } else { + if (null != format) { + belt.finalOutput().warning(String.format("# WARNING: Unknown value for %s: %s", ENV_RD_FORMAT, format)); + } configNiceFormat(belt); } } @@ -139,6 +165,7 @@ public static Tool tool(final Rd rd) { ) .bannerResource("rd-banner.txt") .commandInput(new JewelInput()); + belt.printStackTrace(rd.getDebugLevel() > 0); setupColor(belt, rd); setupFormat(belt, rd); @@ -150,11 +177,13 @@ public static Tool tool(final Rd rd) { belt.finalOutput().warning( "# WARNING: RD_INSECURE_SSL=true, no hostname or certificate trust verification will be performed"); } + rd.setOutput(belt.finalOutput()); return belt.buckle(); } static class Rd extends ExtConfigSource implements RdApp, AppConfig { Client client; + private CommandOutput output; public Rd(final ConfigSource src) { super(src); @@ -171,6 +200,11 @@ public boolean isAnsiEnabled() { ); } + @Override + public int getDebugLevel() { + return getInt(ENV_DEBUG, 0); + } + public String getDateFormat() { return getString("RD_DATE_FORMAT", "yyyy-MM-dd'T'HH:mm:ssXX"); } @@ -183,10 +217,40 @@ public Client getClient() throws InputError { return client; } + @Override + public Client getClient(final int version) throws InputError { + client = Main.createClient(this, version); + return client; + } + @Override public AppConfig getAppConfig() { return this; } + + public void versionDowngradeWarning(int requested, int supported) { + getOutput().warning(String.format( + "# WARNING: API Version Downgraded: %d -> %d", + requested, + supported + )); + getOutput().warning(String.format( + "# WARNING: To avoid this warning, specify the API version via RD_URL: " + + "export RD_URL=%sapi/%s", + client.getAppBaseUrl(), + supported + )); + getOutput().warning("# WARNING: To disable downgrading: " + + "export RD_API_DOWNGRADE=false"); + } + + public CommandOutput getOutput() { + return output; + } + + public void setOutput(CommandOutput output) { + this.output = output; + } } private static void setupColor(final ToolBelt belt, AppConfig config) { @@ -211,7 +275,11 @@ private static void setupColor(final ToolBelt belt, AppConfig config) { } - public static Client createClient(AppConfig config) throws InputError { + public static Client createClient(Rd config) throws InputError { + return createClient(config, null); + } + + public static Client createClient(Rd config, Integer requestedVersion) throws InputError { Auth auth = new Auth() { }; auth = auth.chain(new ConfigAuth(config)); @@ -226,6 +294,14 @@ public static Client createClient(AppConfig config) throws InputErro RundeckClient.Builder builder = RundeckClient.builder() .baseUrl(baseUrl) .config(config); + if (null != requestedVersion) { + builder.apiVersion(requestedVersion); + } else { + int anInt = config.getInt(ENV_API_VERSION, -1); + if (anInt > 0) { + builder.apiVersion(anInt); + } + } if (auth.isTokenAuth()) { builder.tokenAuth(auth.getToken()); @@ -240,6 +316,7 @@ public static Client createClient(AppConfig config) throws InputErro } builder.passwordAuth(auth.getUsername(), auth.getPassword()); } + builder.logger(new OutputLogger(config.getOutput())); return builder.build(); } @@ -452,4 +529,27 @@ public void pond(CommandOutput out) { out.output(kind); } } + + private static class OutputLogger implements Client.Logger { + final CommandOutput output; + + public OutputLogger(final CommandOutput output) { + this.output = output; + } + + @Override + public void output(final String out) { + output.output(out); + } + + @Override + public void warning(final String warn) { + output.warning(warn); + } + + @Override + public void error(final String err) { + output.error(err); + } + } } diff --git a/src/main/java/org/rundeck/client/tool/RdApp.java b/src/main/java/org/rundeck/client/tool/RdApp.java index 424cda5d..9cfdc4ea 100644 --- a/src/main/java/org/rundeck/client/tool/RdApp.java +++ b/src/main/java/org/rundeck/client/tool/RdApp.java @@ -16,6 +16,7 @@ package org.rundeck.client.tool; +import com.simplifyops.toolbelt.CommandOutput; import com.simplifyops.toolbelt.InputError; import org.rundeck.client.api.RundeckApi; import org.rundeck.client.tool.AppConfig; @@ -28,5 +29,11 @@ public interface RdApp { Client getClient() throws InputError; + Client getClient(int version) throws InputError; + AppConfig getAppConfig(); + + public CommandOutput getOutput(); + + public void versionDowngradeWarning(int requested, int supported); } diff --git a/src/main/java/org/rundeck/client/tool/commands/AppCommand.java b/src/main/java/org/rundeck/client/tool/commands/AppCommand.java index 837411ea..58c72073 100644 --- a/src/main/java/org/rundeck/client/tool/commands/AppCommand.java +++ b/src/main/java/org/rundeck/client/tool/commands/AppCommand.java @@ -16,6 +16,7 @@ package org.rundeck.client.tool.commands; +import com.simplifyops.toolbelt.CommandOutput; import com.simplifyops.toolbelt.InputError; import org.rundeck.client.api.RundeckApi; import org.rundeck.client.tool.AppConfig; @@ -42,9 +43,36 @@ public AppConfig getAppConfig() { return rdApp.getAppConfig(); } + @Override + public CommandOutput getOutput() { + return rdApp.getOutput(); + } + + @Override + public Client getClient(final int version) throws InputError { + return rdApp.getClient(version); + } + + @Override + public void versionDowngradeWarning(final int requested, final int supported) { + rdApp.versionDowngradeWarning(requested, supported); + } + public T apiCall(Function> func) throws InputError, IOException { - Client client = getClient(); - return apiCall(client, func); + try { + return getClient().apiCallDowngradable(func); + } catch (Client.UnsupportedVersion unsupportedVersion) { + //degrade to supported version and try again + if (unsupportedVersion.getLatestVersion() < unsupportedVersion.getRequestedVersion()) { + rdApp.versionDowngradeWarning( + unsupportedVersion.getRequestedVersion(), + unsupportedVersion.getLatestVersion() + ); + return rdApp.getClient(unsupportedVersion.getLatestVersion()).apiCall(func); + } else { + throw unsupportedVersion.getRequestFailed(); + } + } } public static T apiCall( @@ -53,7 +81,16 @@ public static T apiCall( ) throws IOException { - return client.checkError(func.apply(client.getService())); + return client.apiCall(func); + } + + public static T apiCallDowngradable( + final Client client, + final Function> func + ) + throws IOException, Client.UnsupportedVersion + { + return client.apiCallDowngradable(func); } public String projectOrEnv(final ProjectNameOptions options) throws InputError { diff --git a/src/main/java/org/rundeck/client/util/Client.java b/src/main/java/org/rundeck/client/util/Client.java index 3c784726..a353bc94 100644 --- a/src/main/java/org/rundeck/client/util/Client.java +++ b/src/main/java/org/rundeck/client/util/Client.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.lang.annotation.Annotation; +import java.util.function.Function; /** * Holds Retrofit and a retrofit-constructed service @@ -55,11 +56,36 @@ public class Client { private T service; private Retrofit retrofit; private final int apiVersion; + private final String appBaseUrl; + private final String apiBaseUrl; + private final boolean allowVersionDowngrade; + private final Logger logger; - public Client(final T service, final Retrofit retrofit, final int apiVersion) { + public static interface Logger { + void output(String out); + + void warning(String warn); + + void error(String err); + } + + public Client( + final T service, + final Retrofit retrofit, + final String appBaseUrl, + final String apiBaseUrl, + final int apiVersion, + final boolean allowVersionDowngrade, + final Logger logger + ) + { this.service = service; this.retrofit = retrofit; + this.appBaseUrl = appBaseUrl; + this.apiBaseUrl = apiBaseUrl; this.apiVersion = apiVersion; + this.allowVersionDowngrade = allowVersionDowngrade; + this.logger = logger; } /** @@ -94,6 +120,67 @@ public R checkError(final Call execute) throws IOException { return checkError(response); } + /** + * Execute the remote call, and return the expected type if successful. if unsuccessful + * throw an exception with relevant error detail + * + * @param execute call + * @param expected result type + * + * @return result + * + * @throws IOException if remote call is unsuccessful or parsing error occurs + */ + public R checkErrorDowngradable(final Call execute) throws IOException, UnsupportedVersion { + Response response = execute.execute(); + return checkErrorDowngradable(response); + } + + /** + * @return the base URL used without API path + */ + public String getAppBaseUrl() { + return appBaseUrl; + } + + /** + * @return the API URL used + */ + public String getApiBaseUrl() { + return apiBaseUrl; + } + + public static final class UnsupportedVersion extends Exception { + private final int requestedVersion; + private final int latestVersion; + private final RequestFailed requestFailed; + + public UnsupportedVersion( + final String message, + final RequestFailed cause, + final int requestedVersion, + final int latestVersion + ) + { + super(message, cause); + this.requestFailed = getRequestFailed(); + this.requestedVersion = requestedVersion; + this.latestVersion = latestVersion; + } + + public int getLatestVersion() { + return latestVersion; + } + + public int getRequestedVersion() { + return requestedVersion; + } + + public RequestFailed getRequestFailed() { + return requestFailed; + } + } + /** * return the expected type if successful. if response is unsuccessful * throw an exception with relevant error detail @@ -106,50 +193,96 @@ public R checkError(final Call execute) throws IOException { * @throws IOException if remote call is unsuccessful or parsing error occurs */ public R checkError(final Response response) throws IOException { + if (!response.isSuccessful()) { + return handleError(response, readError(response)); + } + return response.body(); + } + + /** + * return the expected type if successful. if response is unsuccessful + * throw an exception with relevant error detail + * + * @param response call response + * @param expected type + * + * @return result + * + * @throws IOException if remote call is unsuccessful or parsing error occurs + */ + public R checkErrorDowngradable(final Response response) throws IOException, UnsupportedVersion { if (!response.isSuccessful()) { ErrorDetail error = readError(response); - reportApiError(error); - if (response.code() == 401 || response.code() == 403) { - //authorization - throw new AuthorizationFailed( - String.format("Authorization failed: %d %s", response.code(), response.message()), - response.code(), - response.message() - ); - } - if (response.code() == 409) { - //authorization - throw new RequestFailed(String.format( - "Could not create resource: %d %s", - response.code(), - response.message() - ), response.code(), response.message()); - } - if (response.code() == 404) { - //authorization - throw new RequestFailed(String.format( - "Could not find resource: %d %s", - response.code(), - response.message() - ), response.code(), response.message()); - } - throw new RequestFailed( - String.format("Request failed: %d %s", response.code(), response.message()), + checkUnsupportedVersion(response, error); + return handleError(response, error); + } + return response.body(); + } + + private R handleError(final Response response, final ErrorDetail error) { + reportApiError(error); + throw makeErrorThrowable(response, error); + + } + + private RequestFailed makeErrorThrowable(final Response response, final ErrorDetail error) { + if (response.code() == 401 || response.code() == 403) { + //authorization + return new AuthorizationFailed( + String.format("Authorization failed: %d %s", response.code(), response.message()), response.code(), response.message() ); } - return response.body(); + if (response.code() == 409) { + //authorization + return new RequestFailed(String.format( + "Could not create resource: %d %s", + response.code(), + response.message() + ), response.code(), response.message()); + } + if (response.code() == 404) { + //authorization + return new RequestFailed(String.format( + "Could not find resource: %d %s", + response.code(), + response.message() + ), response.code(), response.message()); + } + return new RequestFailed( + String.format("Request failed: %d %s", response.code(), response.message()), + response.code(), + response.message() + ); + } + + public void checkUnsupportedVersion(final Response response, final ErrorDetail error) + throws UnsupportedVersion + { + if (null != error && allowVersionDowngrade && API_ERROR_API_VERSION_UNSUPPORTED.equals(error.getErrorCode())) { + throw new UnsupportedVersion(error.getErrorMessage(), + makeErrorThrowable(response, error), + getApiVersion(), error.getApiVersion() + ); + } } public void reportApiError(final ErrorDetail error) { if (null != error) { - System.err.printf("Error: %s%n", error); + logger.error(String.format("Error: %s", error)); if (API_ERROR_API_VERSION_UNSUPPORTED.equals(error.getErrorCode())) { - System.err.printf("Note: You requested an API endpoint using an unsupported version. \n" + - "You can set a specific version by using a Rundeck " + - "URL in the format:\n" + - " export RD_URL=[URL]/api/%s\n\n", error.getApiVersion()); + logger.warning(String.format( + "Note: You requested an API endpoint using an unsupported version.\n" + + "You can set a specific version by using a Rundeck URL in the format:\n" + + " export RD_URL=%sapi/%s", + getAppBaseUrl(), + error.getApiVersion() + )); + logger.warning( + "You can enable auto-downgrading to a supported version: \n" + + " export RD_API_DOWNGRADE=true" + ); } } } @@ -212,6 +345,34 @@ public X readError(Response execute, Class errorType, MediaType... med } } + /** + * call a function using the service + * + * @param func function using the service + * @param result type + * + * @return result + * + * @throws IOException if an error occurs + */ + public U apiCall(final Function> func) throws IOException { + return checkError(func.apply(getService())); + } + + /** + * call a function using the service + * + * @param func function using the service + * @param result type + * + * @return result + * + * @throws IOException if an error occurs + */ + public U apiCallDowngradable(final Function> func) throws IOException, UnsupportedVersion { + return checkErrorDowngradable(func.apply(getService())); + } + public T getService() { return service; } diff --git a/src/test/groovy/org/rundeck/client/tool/commands/ExecutionsSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/ExecutionsSpec.groovy index ce97da85..b5bdeb99 100644 --- a/src/test/groovy/org/rundeck/client/tool/commands/ExecutionsSpec.groovy +++ b/src/test/groovy/org/rundeck/client/tool/commands/ExecutionsSpec.groovy @@ -49,7 +49,7 @@ class ExecutionsSpec extends Specification { def api = Mock(RundeckApi) def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 18) + def client = new Client(api, retrofit, null, null, 18, true, null) ExecOutput execOutput = new ExecOutput() execOutput.execState = initState diff --git a/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy index 054073d0..d7ed15ec 100644 --- a/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy +++ b/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy @@ -60,7 +60,7 @@ class JobsSpec extends Specification { getGroup() >> group } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 17) + def client = new Client(api, retrofit, null, null, 17, true, null) def hasclient = Mock(RdApp) { getClient() >> client } @@ -96,7 +96,7 @@ class JobsSpec extends Specification { getFile() >> tempFile } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 17) + def client = new Client(api, retrofit, null, null, 17, true, null) def hasclient = Mock(RdApp) { getClient() >> client } @@ -138,7 +138,7 @@ class JobsSpec extends Specification { isConfirm() >> true } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 17) + def client = new Client(api, retrofit, null, null, 17, true, null) def hasclient = Mock(RdApp) { getClient() >> client } @@ -178,7 +178,7 @@ class JobsSpec extends Specification { isConfirm() >> true } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 17) + def client = new Client(api, retrofit, null, null, 17, true, null) def hasclient = Mock(RdApp) { getClient() >> client } @@ -204,7 +204,7 @@ class JobsSpec extends Specification { } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 18) + def client = new Client(api, retrofit, null, null, 18, true, null) def hasclient = Mock(RdApp) { getClient() >> client } @@ -235,7 +235,7 @@ class JobsSpec extends Specification { getFile() >> tempFile } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 17) + def client = new Client(api, retrofit, null, null, 17, true, null) def hasclient = Mock(RdApp) { getClient() >> client } diff --git a/src/test/groovy/org/rundeck/client/tool/commands/KeysSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/KeysSpec.groovy index 57311c44..c5ff42f7 100644 --- a/src/test/groovy/org/rundeck/client/tool/commands/KeysSpec.groovy +++ b/src/test/groovy/org/rundeck/client/tool/commands/KeysSpec.groovy @@ -168,7 +168,7 @@ class KeysSpec extends Specification { } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 18) + def client = new Client(api, retrofit, null, null, 18, true, null) def hasclient = Mock(RdApp) { getClient() >> client } @@ -230,7 +230,7 @@ class KeysSpec extends Specification { build() def api = retrofit.create(RundeckApi) def out = Mock(CommandOutput) - def client = new Client(api, retrofit, 18) + def client = new Client(api, retrofit, null, null, 18, true, null) def hasclient = Mock(RdApp) { getClient() >> client } diff --git a/src/test/groovy/org/rundeck/client/tool/commands/ProjectsSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/ProjectsSpec.groovy index f5c867ed..cc208c08 100644 --- a/src/test/groovy/org/rundeck/client/tool/commands/ProjectsSpec.groovy +++ b/src/test/groovy/org/rundeck/client/tool/commands/ProjectsSpec.groovy @@ -19,7 +19,6 @@ package org.rundeck.client.tool.commands import com.simplifyops.toolbelt.CommandOutput import org.rundeck.client.api.RundeckApi import org.rundeck.client.api.model.ProjectItem -import org.rundeck.client.api.model.ScheduledJobItem import org.rundeck.client.tool.RdApp import org.rundeck.client.util.Client import retrofit2.Retrofit @@ -42,7 +41,7 @@ class ProjectsSpec extends Specification { } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 18) + def client = new Client(api, retrofit, null, null, 18, true, null) def hasclient = Mock(RdApp) { getClient() >> client } diff --git a/src/test/groovy/org/rundeck/client/tool/commands/RunSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/RunSpec.groovy index e09af7cc..ef9ea79d 100644 --- a/src/test/groovy/org/rundeck/client/tool/commands/RunSpec.groovy +++ b/src/test/groovy/org/rundeck/client/tool/commands/RunSpec.groovy @@ -48,7 +48,7 @@ class RunSpec extends Specification { getJob() >> 'a group/path/a job' } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 17) + def client = new Client(api, retrofit, null, null, 17, true, null) def hasclient = Mock(RdApp) { getClient() >> client } @@ -79,7 +79,7 @@ class RunSpec extends Specification { getJob() >> 'a group/path/a job' } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 17) + def client = new Client(api, retrofit, null, null, 17, true, null) def appConfig = Mock(AppConfig) def hasclient = Mock(RdApp) { getClient() >> client @@ -126,7 +126,7 @@ class RunSpec extends Specification { ].collect { it.toString() } } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 19) + def client = new Client(api, retrofit, null, null, 19, true, null) def appConfig = Mock(AppConfig) def hasclient = Mock(RdApp) { getClient() >> client diff --git a/src/test/groovy/org/rundeck/client/tool/commands/TokensSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/TokensSpec.groovy index f3dd0413..eca1232a 100644 --- a/src/test/groovy/org/rundeck/client/tool/commands/TokensSpec.groovy +++ b/src/test/groovy/org/rundeck/client/tool/commands/TokensSpec.groovy @@ -126,7 +126,7 @@ class TokensSpec extends Specification { isUser() >> true } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 18) + def client = new Client(api, retrofit, null, null, 18, true, null) def hasclient = Mock(RdApp) { getClient() >> client } @@ -162,7 +162,7 @@ class TokensSpec extends Specification { isRoles() >> true } def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 19) + def client = new Client(api, retrofit, null, null, 19, true, null) def hasclient = Mock(RdApp) { getClient() >> client } diff --git a/src/test/groovy/org/rundeck/client/tool/commands/projects/SCMSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/projects/SCMSpec.groovy index 96d9df33..91f4f0f3 100644 --- a/src/test/groovy/org/rundeck/client/tool/commands/projects/SCMSpec.groovy +++ b/src/test/groovy/org/rundeck/client/tool/commands/projects/SCMSpec.groovy @@ -17,9 +17,6 @@ package org.rundeck.client.tool.commands.projects import com.simplifyops.toolbelt.CommandOutput -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import okhttp3.mockwebserver.RecordedRequest import org.rundeck.client.api.RundeckApi import org.rundeck.client.api.model.ScmActionInputsResult import org.rundeck.client.api.model.ScmImportItem @@ -31,7 +28,6 @@ import org.rundeck.client.tool.AppConfig import org.rundeck.client.tool.RdApp import org.rundeck.client.util.Client import retrofit2.Retrofit -import retrofit2.converter.jackson.JacksonConverterFactory import retrofit2.mock.Calls import spock.lang.Specification @@ -45,7 +41,7 @@ class SCMSpec extends Specification { def api = Mock(RundeckApi) def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 18) + def client = new Client(api, retrofit, null, null, 18, true, null) def appConfig = Mock(AppConfig) @@ -130,7 +126,7 @@ class SCMSpec extends Specification { def api = Mock(RundeckApi) def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() - def client = new Client(api, retrofit, 18) + def client = new Client(api, retrofit, null, null, 18, true, null) def appConfig = Mock(AppConfig)