diff --git a/docs/commands.md b/docs/commands.md index cb020adc..8d004559 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -140,6 +140,22 @@ List and manage projects. readme - Manage Project readme scm - Manage Project SCM +### projects scm + +Manage Project SCM + + Available commands: + + config - Get SCM Config for a Project + disable - Disable plugin + enable - Enable plugin + inputs - Get SCM action inputs + perform - Perform SCM action + plugins - List SCM plugins + setup - Setup SCM Config for a Project + setupinputs - Get SCM Setup inputs + status - Get SCM Status for a Project + ## run Run a Job. diff --git a/src/main/java/org/rundeck/client/api/RundeckApi.java b/src/main/java/org/rundeck/client/api/RundeckApi.java index 90b004f1..2ba24fea 100644 --- a/src/main/java/org/rundeck/client/api/RundeckApi.java +++ b/src/main/java/org/rundeck/client/api/RundeckApi.java @@ -9,9 +9,7 @@ import retrofit2.Call; import retrofit2.http.*; -import java.io.File; import java.net.URL; -import java.util.Date; import java.util.List; import java.util.Map; @@ -461,6 +459,127 @@ Call setupScmConfig( @Body RequestBody body ); + /** + * Setup SCM Config for a + * Project + * + * @param project project + * @param integration integration + * + * @return result + */ + @Headers("Accept: application/json") + @GET("project/{project}/scm/{integration}/status") + Call getScmProjectStatus( + @Path("project") String project, + @Path("integration") String integration + ); + + /** + * Get SCM Setup Inputs for a + * Project + * + * @param project project + * @param integration integration + * @param type plugin type + * + * @return result + */ + @Headers("Accept: application/json") + @GET("project/{project}/scm/{integration}/plugin/{type}/input") + Call getScmSetupInputs( + @Path("project") String project, + @Path("integration") String integration, + @Path("type") String type + ); + + /** + * Get SCM Action Inputs for a + * Project + * + * @param project project + * @param integration integration + * @param action plugin type + * + * @return result + */ + @Headers("Accept: application/json") + @GET("project/{project}/scm/{integration}/action/{action}/input") + Call getScmActionInputs( + @Path("project") String project, + @Path("integration") String integration, + @Path("action") String action + ); + + /** + * Get SCM Action Inputs for a + * Project + * + * @param project project + * @param integration integration + * @param action plugin type + * + * @return result + */ + @Headers("Accept: application/json") + @POST("project/{project}/scm/{integration}/action/{action}") + Call performScmAction( + @Path("project") String project, + @Path("integration") String integration, + @Path("action") String action, + @Body ScmActionPerform body + ); + + /** + * Get SCM Action Inputs for a + * Project + * + * @param project project + * @param integration integration + * + * @return result + */ + @Headers("Accept: application/json") + @GET("project/{project}/scm/{integration}/plugins") + Call listScmPlugins( + @Path("project") String project, + @Path("integration") String integration + ); + + /** + * Enable SCM Integration for a + * Project + * + * @param project project + * @param integration integration + * @param type type + * + */ + @Headers("Accept: application/json") + @POST("project/{project}/scm/{integration}/plugin/{type}/enable") + Call enableScmPlugin( + @Path("project") String project, + @Path("integration") String integration, + @Path("type") String type + ); + + /** + * Disable SCM Integration for a + * Project + * + * @param project project + * @param integration integration + * @param type type + * + */ + @Headers("Accept: application/json") + @POST("project/{project}/scm/{integration}/plugin/{type}/disable") + Call disableScmPlugin( + @Path("project") String project, + @Path("integration") String integration, + @Path("type") String type + ); + /** * List Tokens * diff --git a/src/main/java/org/rundeck/client/api/model/ScmActionInputsResult.java b/src/main/java/org/rundeck/client/api/model/ScmActionInputsResult.java new file mode 100644 index 00000000..85f2d45f --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmActionInputsResult.java @@ -0,0 +1,20 @@ +package org.rundeck.client.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; + +/** + * @author greg + * @since 12/13/16 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ScmActionInputsResult { + public String title; + public String description; + public String integration; + public String actionId; + public List fields; + public List importItems; + public List exportItems; +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmActionPerform.java b/src/main/java/org/rundeck/client/api/model/ScmActionPerform.java new file mode 100644 index 00000000..d20b4a3c --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmActionPerform.java @@ -0,0 +1,53 @@ +package org.rundeck.client.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; +import java.util.Map; + +/** + * @author greg + * @since 12/14/16 + */ + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ScmActionPerform { + private Map input; + private List jobs; + private List items; + private List deleted; + + public Map getInput() { + return input; + } + + public void setInput(Map input) { + this.input = input; + } + + public List getJobs() { + return jobs; + } + + public void setJobs(List jobs) { + this.jobs = jobs; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public List getDeleted() { + return deleted; + } + + public void setDeleted(List deleted) { + this.deleted = deleted; + } +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmExportItem.java b/src/main/java/org/rundeck/client/api/model/ScmExportItem.java new file mode 100644 index 00000000..f7174011 --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmExportItem.java @@ -0,0 +1,33 @@ +package org.rundeck.client.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author greg + * @since 12/13/16 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ScmExportItem { + public String itemId; + public String originalId; + public ScmJobItem job; + public Boolean renamed; + public Boolean deleted; + + public Map toMap() { + HashMap map = new HashMap<>(); + map.put("itemId", itemId); + if (null != originalId) { + map.put("originalId", originalId); + } + if (null != job) { + map.put("job", job.toMap()); + } + map.put("renamed", renamed); + map.put("deleted", deleted); + return map; + } +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmImportItem.java b/src/main/java/org/rundeck/client/api/model/ScmImportItem.java new file mode 100644 index 00000000..ca0a622d --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmImportItem.java @@ -0,0 +1,25 @@ +package org.rundeck.client.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author greg + * @since 12/13/16 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ScmImportItem { + public String itemId; + public Boolean tracked; + public ScmJobItem job; + + public Map toMap() { + HashMap map = new HashMap<>(); + map.put("itemId", itemId); + map.put("tracked", tracked); + map.put("job", job.toMap()); + return map; + } +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmInputField.java b/src/main/java/org/rundeck/client/api/model/ScmInputField.java new file mode 100644 index 00000000..f9d2a0c3 --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmInputField.java @@ -0,0 +1,34 @@ +package org.rundeck.client.api.model; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author greg + * @since 12/13/16 + */ +public class ScmInputField { + public String defaultValue; + public String description; + public String name; + public Boolean required; + public Map renderingOptions; + public String scope; + public String title; + public String type; + public List values; + + public Map toMap() { + HashMap map = new HashMap<>(); + map.put("name", name); + map.put("title", title); + map.put("description", description); + map.put("defaultValue", defaultValue); + map.put("required", required); + map.put("scope", scope); + map.put("renderingOptions", renderingOptions); + map.put("values", values); + return map; + } +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmJobItem.java b/src/main/java/org/rundeck/client/api/model/ScmJobItem.java new file mode 100644 index 00000000..5fd2ea85 --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmJobItem.java @@ -0,0 +1,24 @@ +package org.rundeck.client.api.model; + +import org.simpleframework.xml.Element; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author greg + * @since 12/13/16 + */ +public class ScmJobItem { + public String jobId; + public String jobName; + public String groupPath; + + public Map toMap() { + HashMap map = new HashMap<>(); + map.put("jobId", jobId); + map.put("jobName", jobName); + map.put("groupPath", groupPath); + return map; + } +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmPlugin.java b/src/main/java/org/rundeck/client/api/model/ScmPlugin.java new file mode 100644 index 00000000..72c8037f --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmPlugin.java @@ -0,0 +1,26 @@ +package org.rundeck.client.api.model; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author greg + * @since 12/13/16 + */ +public class ScmPlugin { + public String type; + public String title; + public String description; + public Boolean configured; + public Boolean enabled; + + public Map toMap() { + HashMap map = new HashMap<>(); + map.put("type", type); + map.put("title", title); + map.put("description", description); + map.put("configured", configured); + map.put("enabled", enabled); + return map; + } +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmPluginsResult.java b/src/main/java/org/rundeck/client/api/model/ScmPluginsResult.java new file mode 100644 index 00000000..b710405e --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmPluginsResult.java @@ -0,0 +1,12 @@ +package org.rundeck.client.api.model; + +import java.util.List; + +/** + * @author greg + * @since 12/13/16 + */ +public class ScmPluginsResult { + public String integration; + public List plugins; +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmProjectStatusResult.java b/src/main/java/org/rundeck/client/api/model/ScmProjectStatusResult.java new file mode 100644 index 00000000..1c903829 --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmProjectStatusResult.java @@ -0,0 +1,26 @@ +package org.rundeck.client.api.model; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author greg + * @since 12/13/16 + */ +public class ScmProjectStatusResult { + public List actions; + public String integration; + public String message; + public String project; + public ScmSynchState synchState; + + public Map toMap() { + HashMap map = new HashMap<>(); + map.put("message", message); + map.put("synchState", synchState.toString()); + map.put("actions", actions); + + return map; + } +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmSetupInputsResult.java b/src/main/java/org/rundeck/client/api/model/ScmSetupInputsResult.java new file mode 100644 index 00000000..275cc826 --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmSetupInputsResult.java @@ -0,0 +1,13 @@ +package org.rundeck.client.api.model; + +import java.util.List; + +/** + * @author greg + * @since 12/13/16 + */ +public class ScmSetupInputsResult { + public String integration; + public String type; + public List fields; +} diff --git a/src/main/java/org/rundeck/client/api/model/ScmSynchState.java b/src/main/java/org/rundeck/client/api/model/ScmSynchState.java new file mode 100644 index 00000000..227f74e6 --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/ScmSynchState.java @@ -0,0 +1,15 @@ +package org.rundeck.client.api.model; + +/** + * @author greg + * @since 12/13/16 + */ +public enum ScmSynchState { + CLEAN, + UNKNOWN, + REFRESH_NEEDED, + IMPORT_NEEDED, + EXPORT_NEEDED, + DELETE_NEEDED, + CREATE_NEEDED +} diff --git a/src/main/java/org/rundeck/client/tool/commands/ApiCommand.java b/src/main/java/org/rundeck/client/tool/commands/ApiCommand.java index 21c7fb70..8b9211be 100644 --- a/src/main/java/org/rundeck/client/tool/commands/ApiCommand.java +++ b/src/main/java/org/rundeck/client/tool/commands/ApiCommand.java @@ -3,7 +3,11 @@ import com.simplifyops.toolbelt.InputError; import org.rundeck.client.api.RundeckApi; import org.rundeck.client.util.Client; +import retrofit2.Call; +import java.io.IOException; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -28,4 +32,8 @@ public Client getClient() throws InputError { } return client; } + + public T apiCall(Function> func) throws InputError, IOException { + return getClient().checkError(func.apply(getClient().getService())); + } } diff --git a/src/main/java/org/rundeck/client/tool/commands/Projects.java b/src/main/java/org/rundeck/client/tool/commands/Projects.java index 36bfbef5..e0a9d21f 100644 --- a/src/main/java/org/rundeck/client/tool/commands/Projects.java +++ b/src/main/java/org/rundeck/client/tool/commands/Projects.java @@ -6,21 +6,18 @@ import com.simplifyops.toolbelt.CommandOutput; import com.simplifyops.toolbelt.HasSubCommands; import com.simplifyops.toolbelt.InputError; -import org.rundeck.client.api.RundeckApi; import org.rundeck.client.api.model.ProjectItem; import org.rundeck.client.tool.commands.projects.ACLs; import org.rundeck.client.tool.commands.projects.Readme; import org.rundeck.client.tool.commands.projects.SCM; +import org.rundeck.client.tool.options.OptionUtil; import org.rundeck.client.tool.options.ProjectCreateOptions; import org.rundeck.client.tool.options.ProjectNameOptions; -import org.rundeck.client.util.Client; import java.io.IOException; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Supplier; import java.util.stream.Collectors; import static org.rundeck.client.tool.options.OptionUtil.projectOrEnv; @@ -79,20 +76,7 @@ public boolean delete(ProjectDelete options, CommandOutput output) throws IOExce @Command(description = "Create a project.") public boolean create(Create options, CommandOutput output) throws IOException, InputError { - Map config = new HashMap<>(); - if (options.config().size() > 0) { - for (String s : options.config()) { - if (!s.startsWith("--")) { - throw new InputError("Expected --key=value, but saw: " + s); - } - s = s.substring(2); - String[] arr = s.split("=", 2); - if (arr.length != 2) { - throw new InputError("Expected --key=value, but saw: " + s); - } - config.put(arr[0], arr[1]); - } - } + Map config = OptionUtil.parseKeyValueMap(options.config()); ProjectItem project = new ProjectItem(); project.setName(projectOrEnv(options)); project.setConfig(config); diff --git a/src/main/java/org/rundeck/client/tool/commands/projects/SCM.java b/src/main/java/org/rundeck/client/tool/commands/projects/SCM.java index ad9439b9..d96d07f3 100644 --- a/src/main/java/org/rundeck/client/tool/commands/projects/SCM.java +++ b/src/main/java/org/rundeck/client/tool/commands/projects/SCM.java @@ -10,9 +10,9 @@ import okhttp3.RequestBody; import org.rundeck.client.api.RequestFailed; import org.rundeck.client.api.RundeckApi; -import org.rundeck.client.api.model.ScmActionResult; -import org.rundeck.client.api.model.ScmConfig; +import org.rundeck.client.api.model.*; import org.rundeck.client.tool.commands.ApiCommand; +import org.rundeck.client.tool.options.OptionUtil; import org.rundeck.client.util.Client; import org.rundeck.client.util.Colorz; import retrofit2.Call; @@ -20,8 +20,10 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; -import java.util.function.Supplier; +import java.util.List; +import java.util.stream.Collectors; /** * Created by greg on 7/21/16. @@ -62,7 +64,7 @@ public void config(ConfigOptions options, CommandOutput output) throws IOExcepti basic.put("Project", scmConfig1.project); basic.put("SCM Plugin type", scmConfig1.type); basic.put("SCM Plugin integration", scmConfig1.integration); - output.output(Colorz.colorizeMapRecurse(basic, ANSIColorOutput.Color.GREEN)); + output.info(basic); HashMap map = new HashMap<>(); map.put("config", scmConfig1.config); @@ -72,8 +74,7 @@ public void config(ConfigOptions options, CommandOutput output) throws IOExcepti objectMapper.writeValue(options.getFile(), map); output.info("Wrote config to file: " + options.getFile()); } else { - output.output(Colorz.colorizeMapRecurse(map, ANSIColorOutput.Color.GREEN, ANSIColorOutput.Color.YELLOW)); - + output.output(map); } } @@ -100,25 +101,33 @@ public boolean setup(SetupOptions options, CommandOutput output) throws IOExcept //dont use client.checkError, we want to handle 400 validation error Call execute = getClient().getService() .setupScmConfig( - options.getProject(), - options.getIntegration(), - options.getType(), - requestBody - ); + options.getProject(), + options.getIntegration(), + options.getType(), + requestBody + ); Response response = execute.execute(); //check for 400 error with validation information - checkValidationError(output, getClient(), response, options.getFile().getAbsolutePath()); + if (!checkValidationError(output, getClient(), response, + "Setup config Validation for file: " + options.getFile().getAbsolutePath() + )) { + return false; + } //otherwise check other error codes and fail if necessary ScmActionResult result = getClient().checkError(response); + return outputScmActionResult(output, result, "Setup"); + } + + private boolean outputScmActionResult(final CommandOutput output, final ScmActionResult result, final String name) { if (result.success) { - output.info("Setup was successful."); + output.info(name + " was successful."); } else { - output.warning("Setup was not successful."); + output.warning(name + " was not successful."); } if (result.message != null) { output.info("Result: " + result.message); @@ -134,25 +143,222 @@ public boolean setup(SetupOptions options, CommandOutput output) throws IOExcept return result.success; } + @CommandLineInterface(application = "status") + public interface StatusOptions extends BaseScmOptions { + } + + @Command(description = "Get SCM Status for a Project") + public boolean status(StatusOptions options, CommandOutput output) throws IOException, InputError { + ScmProjectStatusResult result = getClient().checkError(getClient().getService() + .getScmProjectStatus( + options.getProject(), + options.getIntegration() + ).execute()); + + + output.output(result.toMap()); + return result.synchState == ScmSynchState.CLEAN; + } + + @CommandLineInterface(application = "enable") + public interface EnableOptions extends BaseScmOptions { + @Option(longName = "type", shortName = "t", description = "Plugin type") + String getType(); + } + + @Command(description = "Enable plugin ") + public void enable(EnableOptions options, CommandOutput output) throws IOException, InputError { + //otherwise check other error codes and fail if necessary + Void result = getClient().checkError(getClient().getService() + .enableScmPlugin( + options.getProject(), + options.getIntegration(), + options.getType() + ).execute()); + + } + + @CommandLineInterface(application = "disable") + public interface DisableOptions extends EnableOptions { + } + + @Command(description = "Disable plugin ") + public void disable(DisableOptions options, CommandOutput output) throws IOException, InputError { + //otherwise check other error codes and fail if necessary + Void result = getClient().checkError(getClient().getService() + .disableScmPlugin( + options.getProject(), + options.getIntegration(), + options.getType() + ).execute()); + + } + + @CommandLineInterface(application = "setupinputs") + public interface InputsOptions extends BaseScmOptions { + @Option(longName = "type", shortName = "t", description = "Plugin type") + String getType(); + } + + @Command(description = "Get SCM Setup inputs") + public void setupinputs(InputsOptions options, CommandOutput output) throws IOException, InputError { + + + //dont use client.checkError, we want to handle 400 validation error + Call execute = getClient().getService() + .getScmSetupInputs( + options.getProject(), + options.getIntegration(), + options.getType() + ); + + + //otherwise check other error codes and fail if necessary + ScmSetupInputsResult result = getClient().checkError(execute.execute()); + + output.output(result.fields.stream().map(ScmInputField::toMap).collect(Collectors.toList())); + } + + @CommandLineInterface(application = "setupinputs") + public interface ActionInputsOptions extends BaseScmOptions { + @Option(longName = "action", shortName = "a", description = "Action ID") + String getAction(); + + } + + @Command(description = "Get SCM action inputs") + public void inputs(ActionInputsOptions options, CommandOutput output) throws IOException, InputError { + + + //dont use client.checkError, we want to handle 400 validation error + Call execute = getClient().getService() + .getScmActionInputs( + options.getProject(), + options.getIntegration(), + options.getAction() + ); + + + //otherwise check other error codes and fail if necessary + ScmActionInputsResult result = getClient().checkError(execute.execute()); + + output.output(result.title + ": " + result.description); + output.output("Fields:"); + output.output(result.fields.stream().map(ScmInputField::toMap).collect(Collectors.toList())); + output.output("Items:"); + if ("export".equals(options.getIntegration())) { + output.output(result.exportItems.stream().map(ScmExportItem::toMap).collect(Collectors.toList())); + } else { + output.output(result.importItems.stream().map(ScmImportItem::toMap).collect(Collectors.toList())); + } + } + + @CommandLineInterface(application = "perform") + public interface ActionPerformOptions extends BaseScmOptions { + @Option(longName = "action", shortName = "a", description = "Action ID") + String getAction(); + + @Option(longName = "field", shortName = "f", description = "Field input values, space separated key=value list") + List getFields(); + + boolean isFields(); + + @Option(longName = "item", shortName = "I", description = "Items to include, space separated list") + List getItem(); + + boolean isItem(); + + @Option(longName = "job", shortName = "j", description = "Job IDs to include, space separated list") + List getJob(); + + boolean isJob(); + + @Option(longName = "delete", + shortName = "d", + description = "Job IDs or Item Ids to delete, space separated list") + List getDelete(); + + boolean isDelete(); + } + + @Command(description = "Perform SCM action") + public boolean perform(ActionPerformOptions options, CommandOutput output) throws IOException, InputError { + + ScmActionPerform perform = performFromOptions(options); + Call execute = getClient().getService().performScmAction( + options.getProject(), + options.getIntegration(), + options.getAction(), + perform + ); + Response response = execute.execute(); + + //check for 400 error with validation information + if (!checkValidationError(output, getClient(), response, + "Action " + options.getAction() + )) { + return false; + } + + //otherwise check other error codes and fail if necessary + ScmActionResult result = getClient().checkError(response); + return outputScmActionResult(output, result, "Action " + options.getAction()); + } + + private ScmActionPerform performFromOptions(final ActionPerformOptions options) throws InputError { + ScmActionPerform perform = new ScmActionPerform(); + if (null != options.getFields()) { + perform.setInput(OptionUtil.parseKeyValueMap(options.getFields(), null, "=")); + } else { + perform.setInput(new HashMap<>()); + } + List item = options.getItem(); + perform.setItems(item != null ? item : new ArrayList<>()); + List job = options.getJob(); + perform.setJobs(job != null ? job : new ArrayList<>()); + List delete = options.getDelete(); + perform.setDeleted(delete != null ? delete : new ArrayList<>()); + return perform; + } + + @CommandLineInterface(application = "plugins") + public interface ListPluginsOptions extends BaseScmOptions { + + } + + @Command(description = "List SCM plugins") + public void plugins(ListPluginsOptions options, CommandOutput output) throws IOException, InputError { + + + //dont use client.checkError, we want to handle 400 validation error + + + //otherwise check other error codes and fail if necessary + ScmPluginsResult result = getClient().checkError(getClient().getService() + .listScmPlugins( + options.getProject(), + options.getIntegration() + ).execute()); + output.output(result.plugins.stream().map(ScmPlugin::toMap).collect(Collectors.toList())); + } + /** * Check for validation info from resposne * * @param output * @param client * @param response - * @param filename - * + * @param name * @throws IOException */ - private static void checkValidationError( + private static boolean checkValidationError( CommandOutput output, final Client client, final Response response, - final String filename + final String name ) throws IOException { - if (!response.isSuccessful()) { if (response.code() == 400) { try { @@ -164,26 +370,28 @@ private static void checkValidationError( ); if (null != error) { // - output.error("Setup config Validation failed for the file: "); - output.output(filename + "\n"); + output.error(name + " failed"); if (null != error.message) { output.warning(error.message); } - output.output(Colorz.colorizeMapRecurse(error.toMap(), ANSIColorOutput.Color.YELLOW)); + if (null != error.toMap()) { + output.output(Colorz.colorizeMapRecurse(error.toMap(), ANSIColorOutput.Color.YELLOW)); + } } } catch (IOException e) { //unable to parse body as expected - System.err.println("Expected SCM Validation error response, but was unable to parse it: " + e); + throw new RequestFailed(String.format( + name + " failed: (error: %d %s)", + response.code(), + response.message() + + ), response.code(), response.message()); } - throw new RequestFailed(String.format( - "Setup config Validation failed: (error: %d %s)", - response.code(), - response.message() - ), response.code(), response.message()); } } + return response.isSuccessful(); } diff --git a/src/main/java/org/rundeck/client/tool/options/OptionUtil.java b/src/main/java/org/rundeck/client/tool/options/OptionUtil.java index ef5f7d82..96182c68 100644 --- a/src/main/java/org/rundeck/client/tool/options/OptionUtil.java +++ b/src/main/java/org/rundeck/client/tool/options/OptionUtil.java @@ -3,6 +3,11 @@ import com.simplifyops.toolbelt.InputError; import org.rundeck.client.util.Env; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + /** * @author greg * @since 11/28/16 @@ -15,4 +20,51 @@ public static String projectOrEnv(ProjectNameOptions options) throws InputError return Env.require("RD_PROJECT", "or specify as `-p/--project value` : Project name."); } + /** + * Parse a list of "--key=value" into a map + * + * @param input + * + * @return + * + * @throws InputError + */ + public static Map parseKeyValueMap(final List input) throws InputError { + return parseKeyValueMap(input, "--", "="); + } + + /** + * Parse a list of "{prefix}key${delim}value" into a map, using specified delimiter and prefix + * + * @param input + * @param keyPrefix + * @param delim + * + * @return + * + * @throws InputError + */ + public static Map parseKeyValueMap( + final List input, + final String keyPrefix, + final String delim + ) throws InputError + { + Map config = new HashMap<>(); + if (input.size() > 0) { + for (String s : input) { + if (keyPrefix != null && !s.startsWith(keyPrefix)) { + throw new InputError(String.format("Expected %skey%svalue, but saw: %s", keyPrefix, delim, s)); + } else if (keyPrefix != null) { + s = s.substring(keyPrefix.length()); + } + String[] arr = s.split(Pattern.quote(delim), 2); + if (arr.length != 2) { + throw new InputError(String.format("Expected %skey%svalue, but saw: %s", keyPrefix, delim, s)); + } + config.put(arr[0], arr[1]); + } + } + return config; + } }