diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/RundeckApi.java b/rd-api-client/src/main/java/org/rundeck/client/api/RundeckApi.java index 8f28cce1..5657deab 100644 --- a/rd-api-client/src/main/java/org/rundeck/client/api/RundeckApi.java +++ b/rd-api-client/src/main/java/org/rundeck/client/api/RundeckApi.java @@ -22,6 +22,9 @@ import org.rundeck.client.api.model.*; import org.rundeck.client.api.model.repository.ArtifactActionMessage; import org.rundeck.client.api.model.repository.RepositoryArtifacts; +import org.rundeck.client.api.model.scheduler.ScheduledJobItem; +import org.rundeck.client.api.model.scheduler.SchedulerTakeover; +import org.rundeck.client.api.model.scheduler.SchedulerTakeoverResult; import org.rundeck.client.util.Json; import org.rundeck.client.util.Xml; import retrofit2.Call; @@ -445,6 +448,13 @@ Call<BulkExecutionDeleteResponse> deleteExecutions( @Body BulkExecutionDelete delete ); + /** + * Delete all executions for a job. + */ + @Headers("Accept: application/json") + @DELETE("job/{id}/executions") + Call<BulkExecutionDeleteResponse> deleteAllJobExecutions(@Path("id") String id); + @Headers("Accept: application/json") @GET("execution/{id}/abort") Call<AbortResult> abortExecution(@Path("id") String id); @@ -453,6 +463,9 @@ Call<BulkExecutionDeleteResponse> deleteExecutions( @GET("execution/{id}") Call<Execution> getExecution(@Path("id") String id); + @Headers("Accept: application/json") + @GET("execution/{id}/state") + Call<ExecutionStateResponse> getExecutionState(@Path("id") String id); @Headers("Accept: application/json") @DELETE("execution/{id}") @@ -739,6 +752,15 @@ Call<Void> deleteSystemAclPolicy( @GET("scheduler/server/{uuid}/jobs") Call<List<ScheduledJobItem>> listSchedulerJobs(@Path("uuid") String uuid); + /** + * Tell a Rundeck server in cluster mode to claim all scheduled jobs from another cluster server. + * + * @see <a href="https://rundeck.org/docs/api/#takeover-schedule-in-cluster-mode">API</a> + */ + @Headers("Accept: application/json") + @PUT("scheduler/takeover") + Call<SchedulerTakeoverResult> takeoverSchedule(@Body SchedulerTakeover schedulerTakeover); + /** * <a href="http://rundeck.org/docs/api/index.html#get-project-scm-config"> @@ -1085,4 +1107,50 @@ Call<Execution> retryJob( @Headers("Accept: application/json") @POST("plugins/uninstall/{pluginId}") Call<ArtifactActionMessage> uninstallPlugin(@Path("pluginId") String pluginId); + + /* Bulk toggle job execution. */ + + /** + * @see <a href="https://rundeck.org/docs/api/#bulk-toggle-job-execution">API</a> + */ + @Json + @Headers("Accept: application/json") + @POST("jobs/execution/enable") + Call<BulkToggleJobExecutionResponse> bulkEnableJobs( + @Body IdList ids + ); + + /** + * @see <a href="https://rundeck.org/docs/api/#bulk-toggle-job-execution">API</a> + */ + @Json + @Headers("Accept: application/json") + @POST("jobs/execution/disable") + Call<BulkToggleJobExecutionResponse> bulkDisableJobs( + @Body IdList ids + ); + + /* Bulk toggle job schedule. */ + + /** + * @see <a href="https://rundeck.org/docs/api/#bulk-toggle-job-schedules">API</a> + */ + @Json + @Headers("Accept: application/json") + @POST("jobs/schedule/enable") + Call<BulkToggleJobScheduleResponse> bulkEnableJobSchedule( + @Body IdList ids + ); + + /** + * @see <a href="https://rundeck.org/docs/api/#bulk-toggle-job-schedules">API</a> + */ + @Json + @Headers("Accept: application/json") + @POST("jobs/schedule/disable") + Call<BulkToggleJobScheduleResponse> bulkDisableJobSchedule( + @Body IdList ids + ); + + } diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/model/BulkToggleJobExecutionResponse.java b/rd-api-client/src/main/java/org/rundeck/client/api/model/BulkToggleJobExecutionResponse.java new file mode 100644 index 00000000..97b73fde --- /dev/null +++ b/rd-api-client/src/main/java/org/rundeck/client/api/model/BulkToggleJobExecutionResponse.java @@ -0,0 +1,117 @@ +/* + * Copyright 2017 Rundeck, Inc. (http://rundeck.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rundeck.client.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BulkToggleJobExecutionResponse { + private int requestCount; + private boolean enabled; + private boolean allsuccessful; + private List<Result> succeeded; + private List<Result> failed; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Result { + private String id; + private String errorCode; + private String message; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return String.format("* #%s: '%s'", id, message); + } + } + + public int getRequestCount() { + return requestCount; + } + + public void setRequestCount(int requestCount) { + this.requestCount = requestCount; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isAllsuccessful() { + return allsuccessful; + } + + public void setAllsuccessful(boolean allsuccessful) { + this.allsuccessful = allsuccessful; + } + + public List<Result> getSucceeded() { + return succeeded; + } + + public void setSucceeded(List<Result> succeeded) { + this.succeeded = succeeded; + } + + public List<Result> getFailed() { + return failed; + } + + public void setFailed(List<Result> failed) { + this.failed = failed; + } + + @Override + public String toString() { + return "BulkToggleJobExecutionResponse{" + + "requestCount=" + requestCount + + ", enabled=" + enabled + + ", allsuccessful=" + allsuccessful + + ", succeeded=" + succeeded + + ", failed=" + failed + + '}'; + } +} diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/model/BulkToggleJobScheduleResponse.java b/rd-api-client/src/main/java/org/rundeck/client/api/model/BulkToggleJobScheduleResponse.java new file mode 100644 index 00000000..5743a700 --- /dev/null +++ b/rd-api-client/src/main/java/org/rundeck/client/api/model/BulkToggleJobScheduleResponse.java @@ -0,0 +1,117 @@ +/* + * Copyright 2017 Rundeck, Inc. (http://rundeck.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rundeck.client.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BulkToggleJobScheduleResponse { + private int requestCount; + private boolean enabled; + private boolean allsuccessful; + private List<Result> succeeded; + private List<Result> failed; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Result { + private String id; + private String errorCode; + private String message; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return String.format("* #%s: '%s'", id, message); + } + } + + public int getRequestCount() { + return requestCount; + } + + public void setRequestCount(int requestCount) { + this.requestCount = requestCount; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isAllsuccessful() { + return allsuccessful; + } + + public void setAllsuccessful(boolean allsuccessful) { + this.allsuccessful = allsuccessful; + } + + public List<Result> getSucceeded() { + return succeeded; + } + + public void setSucceeded(List<Result> succeeded) { + this.succeeded = succeeded; + } + + public List<Result> getFailed() { + return failed; + } + + public void setFailed(List<Result> failed) { + this.failed = failed; + } + + @Override + public String toString() { + return "BulkToggleJobScheduleResponse{" + + "requestCount=" + requestCount + + ", enabled=" + enabled + + ", allsuccessful=" + allsuccessful + + ", succeeded=" + succeeded + + ", failed=" + failed + + '}'; + } +} diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/model/ExecutionStateResponse.java b/rd-api-client/src/main/java/org/rundeck/client/api/model/ExecutionStateResponse.java new file mode 100644 index 00000000..31946f7f --- /dev/null +++ b/rd-api-client/src/main/java/org/rundeck/client/api/model/ExecutionStateResponse.java @@ -0,0 +1,215 @@ +/* + * Copyright 2017 Rundeck, Inc. (http://rundeck.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rundeck.client.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.rundeck.client.util.RdClientConfig; +import org.simpleframework.xml.Element; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ExecutionStateResponse { + + private String executionId; + private String serverNode; + private String executionState; + private Boolean completed; + private Integer stepCount; + private DateInfo updateTime; + private DateInfo startTime; + private DateInfo endTime; + + private List<String> targetNodes; + private List<String> allNodes; + + private Map<String, List<StepState>> nodes; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class StepState { + private String stepctx; + private String executionState; + + public String getStepctx() { + return stepctx; + } + + public void setStepctx(String stepctx) { + this.stepctx = stepctx; + } + + public String getExecutionState() { + return executionState; + } + + public void setExecutionState(String executionState) { + this.executionState = executionState; + } + + @Override + public String toString() { + return "{" + + "stepctx='" + stepctx + '\'' + + ", executionState='" + executionState + '\'' + + '}'; + } + } + + public String execInfoString(RdClientConfig config) { + return String.format( + "%s %s %s %s %s", + executionId, + executionState, + null != startTime ? startTime.format(config.getDateFormat()) : "-", + null != updateTime ? updateTime.format(config.getDateFormat()) : "-", + null != endTime ? endTime.format(config.getDateFormat()) : "-" + ); + } + + public String nodeStatusString() { + if (nodes == null) { + return "No node status."; + } + + return nodes.entrySet().stream() + .map(nodeEntry -> Optional.ofNullable(nodeEntry.getValue()) + .map(steps -> steps.stream() + .map(step -> String.format("%s:%-10s", step.getStepctx(), step.getExecutionState())) + .collect(Collectors.joining( + " ", + String.format("%-15s ",nodeEntry.getKey() + ":"), + "" + ))) + .orElse(nodeEntry.getKey() + ": No Status.")) + .collect(Collectors.joining( + "\n", + "Node Step Status: \n", + "" + )); + } + + @Override + public String toString() { + return "ExecutionStateResponse{" + + "executionId='" + executionId + '\'' + + ", serverNode='" + serverNode + '\'' + + ", executionState='" + executionState + '\'' + + ", completed=" + completed + + ", stepCount=" + stepCount + + ", updateTime=" + updateTime + + ", startTime=" + startTime + + ", endTime=" + endTime + + ", targetNodes=" + targetNodes + + ", allNodes=" + allNodes + + ", nodes=" + nodes + + '}'; + } + + + public String getExecutionId() { + return executionId; + } + + public void setExecutionId(String executionId) { + this.executionId = executionId; + } + + public String getServerNode() { + return serverNode; + } + + public void setServerNode(String serverNode) { + this.serverNode = serverNode; + } + + public String getExecutionState() { + return executionState; + } + + public void setExecutionState(String executionState) { + this.executionState = executionState; + } + + public Boolean getCompleted() { + return completed; + } + + public void setCompleted(Boolean completed) { + this.completed = completed; + } + + public Integer getStepCount() { + return stepCount; + } + + public void setStepCount(Integer stepCount) { + this.stepCount = stepCount; + } + + public DateInfo getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(DateInfo updateTime) { + this.updateTime = updateTime; + } + + public DateInfo getStartTime() { + return startTime; + } + + public void setStartTime(DateInfo startTime) { + this.startTime = startTime; + } + + public DateInfo getEndTime() { + return endTime; + } + + public void setEndTime(DateInfo endTime) { + this.endTime = endTime; + } + + public List<String> getTargetNodes() { + return targetNodes; + } + + public void setTargetNodes(List<String> targetNodes) { + this.targetNodes = targetNodes; + } + + public List<String> getAllNodes() { + return allNodes; + } + + public void setAllNodes(List<String> allNodes) { + this.allNodes = allNodes; + } + + public Map<String, List<StepState>> getNodes() { + return nodes; + } + + public void setNodes(Map<String, List<StepState>> nodes) { + this.nodes = nodes; + } +} diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/model/IdList.java b/rd-api-client/src/main/java/org/rundeck/client/api/model/IdList.java new file mode 100644 index 00000000..b3651511 --- /dev/null +++ b/rd-api-client/src/main/java/org/rundeck/client/api/model/IdList.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Rundeck, Inc. (http://rundeck.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rundeck.client.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class IdList { + private List<String> ids; + + public IdList(final List<String> ids) { + this.ids = ids; + } + + public List<String> getIds() { + return ids; + } + + public void setIds(List<String> ids) { + this.ids = ids; + } +} diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/model/ScheduledJobItem.java b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/ScheduledJobItem.java similarity index 96% rename from rd-api-client/src/main/java/org/rundeck/client/api/model/ScheduledJobItem.java rename to rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/ScheduledJobItem.java index 01c98719..749bba24 100644 --- a/rd-api-client/src/main/java/org/rundeck/client/api/model/ScheduledJobItem.java +++ b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/ScheduledJobItem.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package org.rundeck.client.api.model; +package org.rundeck.client.api.model.scheduler; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.rundeck.client.api.model.JobItem; import java.util.HashMap; import java.util.LinkedHashMap; diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/SchedulerTakeover.java b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/SchedulerTakeover.java new file mode 100644 index 00000000..84196136 --- /dev/null +++ b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/SchedulerTakeover.java @@ -0,0 +1,90 @@ +/* + * Copyright 2017 Rundeck, Inc. (http://rundeck.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rundeck.client.api.model.scheduler; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SchedulerTakeover { + + private TakeoverServerItem server; + private String project; + private JobId job; + + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class JobId { + public String id; + + public String getId() { + return id; + } + + public JobId setId(String id) { + this.id = id; + return this; + } + + @Override + public String toString() { + return "{" + + "id='" + id + '\'' + + '}'; + } + } + + // getters setters + + public TakeoverServerItem getServer() { + return server; + } + + public SchedulerTakeover setServer(TakeoverServerItem server) { + this.server = server; + return this; + } + + public String getProject() { + return project; + } + + public SchedulerTakeover setProject(String project) { + this.project = project; + return this; + } + + public JobId getJob() { + return job; + } + + public SchedulerTakeover setJob(JobId job) { + this.job = job; + return this; + } + + @Override + public String toString() { + return "SchedulerTakeover{" + + "server=" + server + + ", project='" + project + '\'' + + ", job=" + job + + '}'; + } +} diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/SchedulerTakeoverResult.java b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/SchedulerTakeoverResult.java new file mode 100644 index 00000000..f88cb340 --- /dev/null +++ b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/SchedulerTakeoverResult.java @@ -0,0 +1,196 @@ +/* + * Copyright 2017 Rundeck, Inc. (http://rundeck.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rundeck.client.api.model.scheduler; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchedulerTakeoverResult { + + + private String message; + private int apiversion; + private boolean success; + private TakeoverSchedule takeoverSchedule; + private TakeoverSelf self; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class TakeoverSelf { + private TakeoverServerItem server; + + public TakeoverServerItem getServer() { + return server; + } + + public TakeoverSelf setServer(TakeoverServerItem server) { + this.server = server; + return this; + } + + @Override + public String toString() { + return "{" + + "server=" + server + + '}'; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class TakeoverSchedule { + private TakeoverServerItem server; + private String project; + private JobTakeoverResult jobs; + + public TakeoverServerItem getServer() { + return server; + } + + public TakeoverSchedule setServer(TakeoverServerItem server) { + this.server = server; + return this; + } + + public String getProject() { + return project; + } + + public TakeoverSchedule setProject(String project) { + this.project = project; + return this; + } + + public JobTakeoverResult getJobs() { + return jobs; + } + + public TakeoverSchedule setJobs(JobTakeoverResult jobs) { + this.jobs = jobs; + return this; + } + + @Override + public String toString() { + return "{" + + "server=" + server + + ", project='" + project + '\'' + + ", jobs=" + jobs + + '}'; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class JobTakeoverResult { + private int total; + private List<TakeoverJobItem> successful; + private List<TakeoverJobItem> failed; + + public int getTotal() { + return total; + } + + public JobTakeoverResult setTotal(int total) { + this.total = total; + return this; + } + + public List<TakeoverJobItem> getSuccessful() { + return successful; + } + + public JobTakeoverResult setSuccessful(List<TakeoverJobItem> successful) { + this.successful = successful; + return this; + } + + public List<TakeoverJobItem> getFailed() { + return failed; + } + + public JobTakeoverResult setFailed(List<TakeoverJobItem> failed) { + this.failed = failed; + return this; + } + + @Override + public String toString() { + return "{" + + "total=" + total + + ", successful=" + successful + + ", failed=" + failed + + '}'; + } + } + + public String getMessage() { + return message; + } + + public SchedulerTakeoverResult setMessage(String message) { + this.message = message; + return this; + } + + public int getApiversion() { + return apiversion; + } + + public SchedulerTakeoverResult setApiversion(int apiversion) { + this.apiversion = apiversion; + return this; + } + + public boolean isSuccess() { + return success; + } + + public SchedulerTakeoverResult setSuccess(boolean success) { + this.success = success; + return this; + } + + public TakeoverSchedule getTakeoverSchedule() { + return takeoverSchedule; + } + + public SchedulerTakeoverResult setTakeoverSchedule(TakeoverSchedule takeoverSchedule) { + this.takeoverSchedule = takeoverSchedule; + return this; + } + + public TakeoverSelf getSelf() { + return self; + } + + public SchedulerTakeoverResult setSelf(TakeoverSelf self) { + this.self = self; + return this; + } + + @Override + public String toString() { + return "SchedulerTakeoverResult{" + + "message='" + message + '\'' + + ", apiversion=" + apiversion + + ", success=" + success + + ", takeoverSchedule=" + takeoverSchedule + + ", self=" + self + + '}'; + } +} diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/TakeoverJobItem.java b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/TakeoverJobItem.java new file mode 100644 index 00000000..6511eea9 --- /dev/null +++ b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/TakeoverJobItem.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Rundeck, Inc. (http://rundeck.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rundeck.client.api.model.scheduler; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.rundeck.client.api.model.JobItem; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + + +@JsonIgnoreProperties(ignoreUnknown = true) +public class TakeoverJobItem extends JobItem { + private String previousOwner; + + @JsonProperty("previous-owner") + public String getPreviousOwner() { + return previousOwner; + } + + @JsonProperty("previous-owner") + public TakeoverJobItem setPreviousOwner(String previousOwner) { + this.previousOwner = previousOwner; + return this; + } + + + @Override + public String toString() { + return super.toBasicString() + + " previousOwner='" + previousOwner + '\''; + } +} diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/TakeoverServerItem.java b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/TakeoverServerItem.java new file mode 100644 index 00000000..3c9a6bd1 --- /dev/null +++ b/rd-api-client/src/main/java/org/rundeck/client/api/model/scheduler/TakeoverServerItem.java @@ -0,0 +1,38 @@ +package org.rundeck.client.api.model.scheduler; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TakeoverServerItem { + + public String uuid; + public Boolean all; + + public String getUuid() { + return uuid; + } + + public TakeoverServerItem setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public Boolean getAll() { + return all; + } + + public TakeoverServerItem setAll(Boolean all) { + this.all = all; + return this; + } + + @Override + public String toString() { + return "{" + + "uuid='" + uuid + '\'' + + ", all=" + all + + '}'; + } +} diff --git a/src/main/java/org/rundeck/client/tool/Main.java b/src/main/java/org/rundeck/client/tool/Main.java index 83da415b..5bb4dd0f 100644 --- a/src/main/java/org/rundeck/client/tool/Main.java +++ b/src/main/java/org/rundeck/client/tool/Main.java @@ -16,6 +16,7 @@ package org.rundeck.client.tool; +import org.rundeck.client.api.model.scheduler.ScheduledJobItem; import org.rundeck.client.tool.commands.repository.Plugins; import org.rundeck.toolbelt.*; import org.rundeck.toolbelt.format.json.jackson.JsonFormatter; diff --git a/src/main/java/org/rundeck/client/tool/commands/Executions.java b/src/main/java/org/rundeck/client/tool/commands/Executions.java index 8a6b75a5..92f6385b 100644 --- a/src/main/java/org/rundeck/client/tool/commands/Executions.java +++ b/src/main/java/org/rundeck/client/tool/commands/Executions.java @@ -240,6 +240,24 @@ public void info(Info options, CommandOutput out) throws IOException, InputError outputExecutionList(options, out, getAppConfig(), Collections.singletonList(execution).stream()); } + + /* + Executions state command + */ + @CommandLineInterface(application = "state") interface State extends ExecutionIdOption { + } + + @Command(description = "Get detail about the node and step state of an execution by ID.") + public void state(State options, CommandOutput out) throws IOException, InputError { + ExecutionStateResponse response = apiCall(api -> api.getExecutionState(options.getId())); + out.info(response.execInfoString(getAppConfig())); + out.output(response.nodeStatusString()); + } + + + /* END state command */ + + @CommandLineInterface(application = "list") interface ListCmd extends ExecutionListOptions, ProjectNameOptions, ExecutionResultOptions { @@ -573,6 +591,48 @@ public static void outputExecutionList( executions.forEach(e -> out.output(outformat.apply(e))); } + + // Delete All executions for job command. + @CommandLineInterface(application = "deleteall") interface DeleteAllExecCmd { + @Option(longName = "confirm", shortName = "y", description = "Force confirmation of delete request.") + boolean isConfirm(); + + @Option(shortName = "i", longName = "id", description = "Job ID") + String getId(); + } + + @Command(description = "Delete all executions for a job.") + public boolean deleteall(DeleteAllExecCmd options, CommandOutput out) 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 (!"y".equals(s)) { + out.warning("Not deleting executions."); + return false; + } + } + + BulkExecutionDeleteResponse result = apiCall(api -> api.deleteAllJobExecutions(options.getId())); + if (!result.isAllsuccessful()) { + out.error(String.format("Failed to delete %d executions:", result.getFailedCount())); + out.error(result.getFailures() + .stream() + .map(BulkExecutionDeleteResponse.DeleteFailure::toString) + .collect(Collectors.toList())); + }else{ + out.info(String.format("Deleted %d executions.", result.getSuccessCount())); + } + return result.isAllsuccessful(); + } + + + + + // End Delete all executions. + + @CommandLineInterface(application = "deletebulk") interface BulkDeleteCmd extends QueryCmd { @Option(longName = "confirm", shortName = "y", description = "Force confirmation of delete request.") boolean isConfirm(); diff --git a/src/main/java/org/rundeck/client/tool/commands/Jobs.java b/src/main/java/org/rundeck/client/tool/commands/Jobs.java index effbce38..d053d842 100644 --- a/src/main/java/org/rundeck/client/tool/commands/Jobs.java +++ b/src/main/java/org/rundeck/client/tool/commands/Jobs.java @@ -18,6 +18,7 @@ import com.lexicalscope.jewel.cli.CommandLineInterface; import com.lexicalscope.jewel.cli.Option; +import org.rundeck.client.api.model.scheduler.ScheduledJobItem; import org.rundeck.toolbelt.Command; import org.rundeck.toolbelt.CommandOutput; import org.rundeck.toolbelt.HasSubCommands; @@ -189,7 +190,7 @@ interface JobResultOptions extends JobOutputFormatOption, VerboseOption { } - @CommandLineInterface(application = "list") interface ListOpts extends JobListOptions, JobResultOptions { + @CommandLineInterface(application = "list") interface ListOpts extends JobListOptions, JobFileOptions, JobResultOptions { } @Command(description = "List jobs found in a project, or download Job definitions (-f).") @@ -375,4 +376,228 @@ public static String[] splitJobNameParts(final String job) { return new String[]{group, name}; } + + /* Bulk toggle execution */ + + private List<String> getJobList(BulkJobActionOptions options) throws InputError, IOException { + + //if id,idlist specified, use directly + //otherwise query for the list and assemble the ids + + List<String> ids = new ArrayList<>(); + if (options.isIdlist()) { + ids = Arrays.asList(options.getIdlist().split("\\s*,\\s*")); + } + else { + if (!options.isJob() && !options.isGroup() && !options.isGroupExact() && !options.isJobExact()) { + throw new InputError("must specify -i, or -j/-g/-J/-G to specify jobs to enable."); + } + String project = projectOrEnv(options); + List<JobItem> body = apiCall(api -> api.listJobs( + project, + options.getJob(), + options.getGroup(), + options.getJobExact(), + options.getGroupExact() + )); + for (JobItem jobItem : body) { + ids.add(jobItem.getId()); + } + } + + return ids; + } + + + @CommandLineInterface(application = "enablebulk") + interface EnableBulk extends BulkJobActionOptions, VerboseOption { + } + + @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(EnableBulk options, CommandOutput output) throws IOException, InputError { + + List<String> ids = getJobList(options); + + if (!options.isConfirm()) { + //request confirmation + if (null == System.console()) { + output.error("No user interaction available. Use --confirm to confirm request without user interaction"); + output.warning(String.format("Not enabling %d jobs", ids.size())); + return false; + } + String s = System.console().readLine("Really enable %d Jobs? (y/N) ", ids.size()); + + if (!"y".equals(s)) { + output.warning(String.format("Not enabling %d jobs", ids.size())); + return false; + } + } + + final List<String> finalIds = ids; + + BulkToggleJobExecutionResponse response = apiCall(api -> api.bulkEnableJobs(new IdList(finalIds))); + + if (response.isAllsuccessful()) { + output.info(String.format("%d Jobs were enabled%n", response.getRequestCount())); + if (options.isVerbose()) { + output.output(response.getSucceeded().stream() + .map(BulkToggleJobExecutionResponse.Result::toString) + .collect(Collectors.toList())); + } + return true; + } + output.error(String.format("Failed to enable %d Jobs%n", response.getFailed().size())); + output.output(response.getFailed().stream() + .map(BulkToggleJobExecutionResponse.Result::toString) + .collect(Collectors.toList())); + return false; + } + + + @CommandLineInterface(application = "disablebulk") + interface DisableBulk extends BulkJobActionOptions, VerboseOption { + } + + @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(DisableBulk options, CommandOutput output) throws IOException, InputError { + + List<String> ids = getJobList(options); + + if (!options.isConfirm()) { + //request confirmation + if (null == System.console()) { + output.error("No user interaction available. Use --confirm to confirm request without user interaction"); + output.warning(String.format("Not disabling %d jobs", ids.size())); + return false; + } + String s = System.console().readLine("Really disable %d Jobs? (y/N) ", ids.size()); + + if (!"y".equals(s)) { + output.warning(String.format("Not disabling %d jobs", ids.size())); + return false; + } + } + + final List<String> finalIds = ids; + + BulkToggleJobExecutionResponse response = apiCall(api -> api.bulkDisableJobs(new IdList(finalIds))); + + if (response.isAllsuccessful()) { + output.info(String.format("%d Jobs were disabled%n", response.getRequestCount())); + if (options.isVerbose()) { + output.output(response.getSucceeded().stream() + .map(BulkToggleJobExecutionResponse.Result::toString) + .collect(Collectors.toList())); + } + return true; + } + output.error(String.format("Failed to disable %d Jobs%n", response.getFailed().size())); + output.output(response.getFailed().stream() + .map(BulkToggleJobExecutionResponse.Result::toString) + .collect(Collectors.toList())); + return false; + } + + + @CommandLineInterface(application = "reschedulebulk") + interface RescheduleBulk extends BulkJobActionOptions, VerboseOption { + } + + + @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(RescheduleBulk options, CommandOutput output) throws IOException, InputError { + + List<String> ids = getJobList(options); + + if (!options.isConfirm()) { + //request confirmation + if (null == System.console()) { + output.error("No user interaction available. Use --confirm to confirm request without user interaction"); + output.warning(String.format("Not rescheduling %d jobs", ids.size())); + return false; + } + String s = System.console().readLine("Really reschedule %d Jobs? (y/N) ", ids.size()); + + if (!"y".equals(s)) { + output.warning(String.format("Not rescheduling %d jobs", ids.size())); + return false; + } + } + + final List<String> finalIds = ids; + + BulkToggleJobScheduleResponse response = apiCall(api -> api.bulkEnableJobSchedule(new IdList(finalIds))); + + if (response.isAllsuccessful()) { + output.info(String.format("%d Jobs were rescheduled%n", response.getRequestCount())); + if (options.isVerbose()) { + output.output(response.getSucceeded().stream() + .map(BulkToggleJobScheduleResponse.Result::toString) + .collect(Collectors.toList())); + } + return true; + } + output.error(String.format("Failed to reschedule %d Jobs%n", response.getFailed().size())); + output.output(response.getFailed().stream() + .map(BulkToggleJobScheduleResponse.Result::toString) + .collect(Collectors.toList())); + return false; + } + + + @CommandLineInterface(application = "unschedulebulk") + interface UnscheduleBulk extends BulkJobActionOptions, VerboseOption { + } + + + @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(UnscheduleBulk options, CommandOutput output) throws IOException, InputError { + + List<String> ids = getJobList(options); + + if (!options.isConfirm()) { + //request confirmation + if (null == System.console()) { + output.error("No user interaction available. Use --confirm to confirm request without user interaction"); + output.warning(String.format("Not unscheduling %d jobs", ids.size())); + return false; + } + String s = System.console().readLine("Really unschedule %d Jobs? (y/N) ", ids.size()); + + if (!"y".equals(s)) { + output.warning(String.format("Not unscheduling %d jobs", ids.size())); + return false; + } + } + + final List<String> finalIds = ids; + + BulkToggleJobScheduleResponse response = apiCall(api -> api.bulkDisableJobSchedule(new IdList(finalIds))); + + if (response.isAllsuccessful()) { + output.info(String.format("%d Jobs were unsheduled%n", response.getRequestCount())); + if (options.isVerbose()) { + output.output(response.getSucceeded().stream() + .map(BulkToggleJobScheduleResponse.Result::toString) + .collect(Collectors.toList())); + } + return true; + } + output.error(String.format("Failed to disable %d Jobs%n", response.getFailed().size())); + output.output(response.getFailed().stream() + .map(BulkToggleJobScheduleResponse.Result::toString) + .collect(Collectors.toList())); + return false; + } + + + } diff --git a/src/main/java/org/rundeck/client/tool/commands/Scheduler.java b/src/main/java/org/rundeck/client/tool/commands/Scheduler.java index a3be1cab..bff56683 100644 --- a/src/main/java/org/rundeck/client/tool/commands/Scheduler.java +++ b/src/main/java/org/rundeck/client/tool/commands/Scheduler.java @@ -18,10 +18,11 @@ import com.lexicalscope.jewel.cli.CommandLineInterface; import com.lexicalscope.jewel.cli.Option; +import org.rundeck.client.api.model.scheduler.*; +import org.rundeck.client.tool.options.VerboseOption; import org.rundeck.toolbelt.Command; import org.rundeck.toolbelt.CommandOutput; import org.rundeck.toolbelt.InputError; -import org.rundeck.client.api.model.ScheduledJobItem; import org.rundeck.client.tool.RdApp; import java.io.IOException; @@ -33,32 +34,133 @@ */ @Command(description = "View scheduler information") public class Scheduler extends AppCommand { - public Scheduler(final RdApp client) { - super(client); + public Scheduler(final RdApp client) { + super(client); + } + + + @CommandLineInterface(application = "jobs") + interface SchedulerJobs { + + @Option(shortName = "u", + longName = "uuid", + description = "Server UUID to query, or blank to select the target server") + String getUuid(); + + boolean isUuid(); + } + + @Command(description = "List jobs for the current target server, or a specified server.") + public void jobs(SchedulerJobs options, CommandOutput output) throws IOException, InputError { + List<ScheduledJobItem> jobInfo = apiCall(api -> { + if (options.isUuid()) { + return api.listSchedulerJobs(options.getUuid()); + } + else { + return api.listSchedulerJobs(); + } + + }); + output.output(jobInfo.stream().map(ScheduledJobItem::toMap).collect(Collectors.toList())); + } + + + @CommandLineInterface(application = "takeover") + interface Takeover extends VerboseOption { + + @Option(shortName = "u", + longName = "uuid", + description = "Server UUID to take over.") + String getUuid(); + + boolean isUuid(); + + @Option(shortName = "a", + longName = "all", + description = "Take over all jobs regardless of server UUID.") + boolean isAllServers(); + + @Option(shortName = "p", + longName = "project", + description = "Take over only jobs matching the given project name, in combination with --all/-a or --uuid/-u.") + String projectName(); + + boolean isProjectName(); + + @Option(shortName = "j", + longName = "job", + description = "Job UUID to takeover only a single Job’s schedule.") + String getJobId(); + + boolean isJobId(); + + } + + @Command(description = "Tell a Rundeck server in cluster mode to claim all scheduled jobs from another cluster server. " + + "Use --job/-j to specify a job uuid, or alternatively use --uuid/-u, --all/-a, --project/-p to " + + "specify a server/project combination.") + public void takeover(Takeover options, CommandOutput output) throws IOException, InputError { + + SchedulerTakeover takeoverParams = new SchedulerTakeover(); + + if (options.isJobId()) { + // Takeover by job uuid. + takeoverParams.setJob(new SchedulerTakeover.JobId() + .setId(options.getJobId()) + ); } + else { + // Takeover by server params. + if (options.isAllServers()) { + takeoverParams.setServer(new TakeoverServerItem() + .setAll(true)); + } + else if (options.isUuid()) { + takeoverParams.setServer(new TakeoverServerItem() + .setUuid(options.getUuid())); + } + else { + throw new InputError("Must specify -u, or -a or -j to specify jobs to takeover."); + } + + if (options.isProjectName()) { + takeoverParams.setProject(options.projectName()); + } + } - @CommandLineInterface(application = "jobs") interface SchedulerJobs { +// output.info(takeoverParams.toString()); + SchedulerTakeoverResult response = apiCall(api -> api.takeoverSchedule(takeoverParams)); + // print output. + output.info(response.getMessage()); - @Option(shortName = "u", - longName = "uuid", - description = "Server UUID to query, or blank to select the target server") - String getUuid(); + if(response.getTakeoverSchedule() != null + && response.getTakeoverSchedule().getJobs() != null + && response.getTakeoverSchedule().getJobs().getFailed() != null + && response.getTakeoverSchedule().getJobs().getFailed().size() > 0) { - boolean isUuid(); + output.error(String.format("Failed to takeover %d Jobs%n", response.getTakeoverSchedule().getJobs().getFailed().size())); + output.output(response.getTakeoverSchedule().getJobs().getFailed().stream() + .map(TakeoverJobItem::toString) + .collect(Collectors.toList())); } - @Command(description = "List jobs for the current target server, or a specified server.") - public void jobs(SchedulerJobs options, CommandOutput output) throws IOException, InputError { - List<ScheduledJobItem> jobInfo = apiCall(api -> { - if (options.isUuid()) { - return api.listSchedulerJobs(options.getUuid()); - } else { - return api.listSchedulerJobs(); - } - - }); - output.output(jobInfo.stream().map(ScheduledJobItem::toMap).collect(Collectors.toList())); + if (options.isVerbose()) { + if(response.getTakeoverSchedule() != null + && response.getTakeoverSchedule().getJobs() != null + && response.getTakeoverSchedule().getJobs().getSuccessful() != null + && response.getTakeoverSchedule().getJobs().getSuccessful().size() > 0) { + + output.info(String.format("Successfully taken over %d Jobs%n", response.getTakeoverSchedule().getJobs().getSuccessful().size())); + output.output(response.getTakeoverSchedule().getJobs().getSuccessful().stream() + .map(TakeoverJobItem::toString) + .collect(Collectors.toList())); + } + else { + output.warning("* No jobs were taken over."); + } } + } + } diff --git a/src/main/java/org/rundeck/client/tool/options/BulkJobActionOptions.java b/src/main/java/org/rundeck/client/tool/options/BulkJobActionOptions.java new file mode 100644 index 00000000..0cdcc8df --- /dev/null +++ b/src/main/java/org/rundeck/client/tool/options/BulkJobActionOptions.java @@ -0,0 +1,10 @@ +package org.rundeck.client.tool.options; + +import com.lexicalscope.jewel.cli.Option; + +public interface BulkJobActionOptions extends JobListOptions { + + @Option(longName = "confirm", shortName = "y", description = "Force confirmation of request.") + boolean isConfirm(); + +} diff --git a/src/main/java/org/rundeck/client/tool/options/JobBaseOptions.java b/src/main/java/org/rundeck/client/tool/options/JobFileOptions.java similarity index 95% rename from src/main/java/org/rundeck/client/tool/options/JobBaseOptions.java rename to src/main/java/org/rundeck/client/tool/options/JobFileOptions.java index b4c57b02..37571e59 100644 --- a/src/main/java/org/rundeck/client/tool/options/JobBaseOptions.java +++ b/src/main/java/org/rundeck/client/tool/options/JobFileOptions.java @@ -20,7 +20,7 @@ import java.io.File; -public interface JobBaseOptions extends ProjectNameOptions { +public interface JobFileOptions { @Option(shortName = "f", longName = "file", diff --git a/src/main/java/org/rundeck/client/tool/options/JobListOptions.java b/src/main/java/org/rundeck/client/tool/options/JobListOptions.java index 7a975e7c..dad31d58 100644 --- a/src/main/java/org/rundeck/client/tool/options/JobListOptions.java +++ b/src/main/java/org/rundeck/client/tool/options/JobListOptions.java @@ -18,7 +18,7 @@ import com.lexicalscope.jewel.cli.Option; -public interface JobListOptions extends JobBaseOptions{ +public interface JobListOptions extends ProjectNameOptions { @Option(shortName = "j", longName = "job", description = "Job name filter") diff --git a/src/main/java/org/rundeck/client/tool/options/JobLoadOptions.java b/src/main/java/org/rundeck/client/tool/options/JobLoadOptions.java index 2be2d267..bc72bfab 100644 --- a/src/main/java/org/rundeck/client/tool/options/JobLoadOptions.java +++ b/src/main/java/org/rundeck/client/tool/options/JobLoadOptions.java @@ -18,7 +18,7 @@ import com.lexicalscope.jewel.cli.Option; -public interface JobLoadOptions extends JobBaseOptions { +public interface JobLoadOptions extends JobFileOptions, ProjectNameOptions { @Option(shortName = "d", longName = "duplicate", 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 2e638788..77598c2c 100644 --- a/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy +++ b/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy @@ -26,7 +26,7 @@ import org.rundeck.client.api.model.DeleteJobsResult import org.rundeck.client.api.model.ImportResult import org.rundeck.client.api.model.JobItem import org.rundeck.client.api.model.JobLoadItem -import org.rundeck.client.api.model.ScheduledJobItem +import org.rundeck.client.api.model.scheduler.ScheduledJobItem import org.rundeck.client.tool.RdApp import org.rundeck.client.util.Client import retrofit2.Retrofit diff --git a/src/test/groovy/org/rundeck/client/tool/commands/SchedulerSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/SchedulerSpec.groovy new file mode 100644 index 00000000..8a98ef39 --- /dev/null +++ b/src/test/groovy/org/rundeck/client/tool/commands/SchedulerSpec.groovy @@ -0,0 +1,120 @@ +/* + * Copyright 2017 Rundeck, Inc. (http://rundeck.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rundeck.client.tool.commands + +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import org.rundeck.client.api.RundeckApi +import org.rundeck.client.api.model.* +import org.rundeck.client.api.model.scheduler.SchedulerTakeover +import org.rundeck.client.tool.RdApp +import org.rundeck.client.util.Client +import org.rundeck.client.util.RdClientConfig +import org.rundeck.toolbelt.CommandOutput +import retrofit2.Retrofit +import retrofit2.converter.jackson.JacksonConverterFactory +import retrofit2.mock.Calls +import spock.lang.Specification + +/** + * @author greg + * @since 12/5/16 + */ +class SchedulerSpec extends Specification { + + def "parse takeover"() { + given: + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setBody('''{ + "takeoverSchedule": { + "jobs": { + "failed": [ + { + "href": "http://dignan:4440/api/14/job/a1aa53ac-73a6-4ead-bbe4-34afbff8e057", + "permalink": "http://dignan:4440/job/show/a1aa53ac-73a6-4ead-bbe4-34afbff8e057", + "id": "11111111-73a6-4ead-1111-34afbff8e057", + "previous-owner": "8F3D5976-2232-4529-847B-8E45764608E3" + }, + { + "href": "http://dignan:4440/api/14/job/116e2025-7895-444a-88f7-d96b4f19fdb3", + "permalink": "http://dignan:4440/job/show/116e2025-7895-444a-88f7-d96b4f19fdb3", + "id": "116e2025-7895-444a-88f7-d96b4f19fdb3", + "previous-owner": "8F3D5976-2232-4529-847B-8E45764608E3" + } + ], + "successful": [ + { + "href": "http://dignan:4440/api/14/job/a1aa53ac-73a6-4ead-bbe4-34afbff8e057", + "permalink": "http://dignan:4440/job/show/a1aa53ac-73a6-4ead-bbe4-34afbff8e057", + "id": "a1aa53ac-73a6-4ead-bbe4-34afbff8e057", + "previous-owner": "8F3D5976-2232-4529-847B-8E45764608E3" + }, + { + "href": "http://dignan:4440/api/14/job/116e2025-7895-444a-88f7-d96b4f19fdb3", + "permalink": "http://dignan:4440/job/show/116e2025-7895-444a-88f7-d96b4f19fdb3", + "id": "116e2025-7895-444a-88f7-d96b4f19fdb3", + "previous-owner": "8F3D5976-2232-4529-847B-8E45764608E3" + } + ], + "total": 4 + }, + "server": { + "uuid": "8F3D5976-2232-4529-847B-8E45764608E3" + } + }, + "self": { + "server": { + "uuid": "C677C663-F902-4B97-B8AC-4AA57B58DDD6" + } + }, + "message": "Schedule Takeover successful for 2/2 Jobs.", + "apiversion": 14, + "success": true +}''' + ).addHeader('content-type', 'application/json') + ); + server.start() + + def retrofit = new Retrofit.Builder().baseUrl(server.url('/api/14/')). + addConverterFactory(JacksonConverterFactory.create()). + build() + def api = retrofit.create(RundeckApi) + + when: + def body = api.takeoverSchedule(new SchedulerTakeover()).execute().body() + + then: + RecordedRequest request1 = server.takeRequest() + request1.path == '/api/14/scheduler/takeover' + + + body.apiversion == 14 + body.success == true + body.takeoverSchedule != null + body.takeoverSchedule.jobs != null + body.takeoverSchedule.jobs.total == 4 + body.takeoverSchedule.jobs.successful.size() == 2 + body.takeoverSchedule.jobs.failed.size() == 2 + body.takeoverSchedule.jobs.successful.get(0).id == "a1aa53ac-73a6-4ead-bbe4-34afbff8e057" + body.takeoverSchedule.jobs.failed.get(0).id == "11111111-73a6-4ead-1111-34afbff8e057" + body.takeoverSchedule.jobs.failed.get(0).previousOwner == "8F3D5976-2232-4529-847B-8E45764608E3" + server.shutdown() + + } + +}