From 8ca15f13ecf9eaac9408b65f81100d479d1445b0 Mon Sep 17 00:00:00 2001 From: jtobard Date: Tue, 22 Aug 2017 14:24:30 -0300 Subject: [PATCH 1/3] base to delete executions --- .../dtolabs/rundeck/core/jobs/JobService.java | 27 ++++++++++++++++++- .../rundeck/services/FrameworkService.groovy | 9 +++++++ .../rundeck/services/JobStateService.groovy | 21 +++++++++++++-- .../jobs/AuthorizingJobService.groovy | 8 +++++- .../jobs/ResolvedAuthJobService.groovy | 9 +++++++ 5 files changed, 70 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/dtolabs/rundeck/core/jobs/JobService.java b/core/src/main/java/com/dtolabs/rundeck/core/jobs/JobService.java index 6403608ea4e..ff4fce7af5c 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/jobs/JobService.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/jobs/JobService.java @@ -19,8 +19,10 @@ import com.dtolabs.rundeck.core.execution.ExecutionNotFound; import com.dtolabs.rundeck.core.execution.ExecutionReference; +import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.Map; /** * Service for interacting with Jobs @@ -76,7 +78,22 @@ public interface JobService { * @return a list of references to executions using the input parameters * */ - List searchExecutions(String state, String project, String jobUuid, String excludeJobUuid, String since); + List searchExecutions(String state, String project, String jobUuid, String excludeJobUuid, + String since); + + /** + * @param state to search + * @param project the project + * @param jobUuid to search or null + * @param excludeJobUuid to search or null + * @param since to search or null + * @param reverseSince if true search executions older than since parameter + * + * @return a list of references to executions using the input parameters + * + */ + List searchExecutions(String state, String project, String jobUuid, String excludeJobUuid, + String since, boolean reverseSince); /** * @param id execution id @@ -97,4 +114,12 @@ public interface JobService { * @return Id of the result execution */ String startJob(JobReference jobReference, String jobArgString, String jobFilter, String asUser)throws JobNotFound; + + /** + * + * @param ids collection of id to iterate + * @param asUser user to execute delete (null for the same user) + * @return [success:true/false, failures:[ [success:false, message: String, id: id],... ], successTotal:Integer] + */ + Map deleteBulkExecutionIds(Collection ids, String asUser); } diff --git a/rundeckapp/grails-app/services/rundeck/services/FrameworkService.groovy b/rundeckapp/grails-app/services/rundeck/services/FrameworkService.groovy index 7bc5c25ff3f..4eadde08b65 100644 --- a/rundeckapp/grails-app/services/rundeck/services/FrameworkService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/FrameworkService.groovy @@ -1056,4 +1056,13 @@ class FrameworkService implements ApplicationContextAware { Map kickJob(ScheduledExecution scheduledExecution, UserAndRolesAuthContext authContext, String user, Map input){ executionService.executeJob(scheduledExecution, authContext, user, input) } + + /** + * non transactional interface to bulk delete executions + * {@link ExecutionService#deleteBulkExecutionIds deleteBulkExecutionIds} + * @return [success:true/false, failures:[ [success:false, message: String, id: id],... ], successTotal:Integer] + */ + Map deleteBulkExecutionIds(Collection ids, AuthContext authContext, String username) { + executionService.deleteBulkExecutionIds(ids,authContext,username) + } } diff --git a/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy b/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy index 3d6096b0797..de6f41596ae 100644 --- a/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy @@ -122,11 +122,17 @@ class JobStateService implements AuthorizingJobService { @Override List searchExecutions(AuthContext auth, String state, String project, String jobUuid, String excludeJobUuid, String since){ + searchExecutions(auth,state,project,jobUuid,excludeJobUuid,since,false) + } + List searchExecutions(AuthContext auth, String state, String project, String jobUuid, + String excludeJobUuid, String since, boolean reverseSince){ def executions = Execution.createCriteria().list { eq('project',project) - eq('status', state) + if(state){ + eq('status', state) + } createAlias('scheduledExecution', 'se') if(jobUuid){ isNotNull 'scheduledExecution' @@ -142,7 +148,12 @@ class JobStateService implements AuthorizingJobService { long timeAgo = Sizes.parseTimeDuration(since,TimeUnit.MILLISECONDS) Date sinceDt = new Date() sinceDt.setTime(sinceDt.getTime()-timeAgo) - ge 'dateStarted',sinceDt + if(reverseSince){ + le 'dateStarted',sinceDt + }else{ + ge 'dateStarted',sinceDt + } + } } @@ -258,4 +269,10 @@ class JobStateService implements AuthorizingJobService { } return resultExecution } + + + @Override + Map deleteBulkExecutionIds(AuthContext auth, Collection ids, String asUser){ + frameworkService.deleteBulkExecutionIds(ids,auth, asUser) + } } diff --git a/rundeckapp/grails-app/services/rundeck/services/jobs/AuthorizingJobService.groovy b/rundeckapp/grails-app/services/rundeck/services/jobs/AuthorizingJobService.groovy index 107db3f1740..2d1580c13fe 100644 --- a/rundeckapp/grails-app/services/rundeck/services/jobs/AuthorizingJobService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/jobs/AuthorizingJobService.groovy @@ -36,10 +36,16 @@ interface AuthorizingJobService { JobState getJobState(AuthContext auth, JobReference jobReference) throws JobNotFound; - List searchExecutions(AuthContext auth, String state, String project, String jobUuid, String excludeJobUuid, String since) + List searchExecutions(AuthContext auth, String state, String project, String jobUuid, + String excludeJobUuid, String since) + + List searchExecutions(AuthContext auth, String state, String project, String jobUuid, + String excludeJobUuid, String since, boolean reverseSince) ExecutionReference executionForId(AuthContext auth, String id, String project) throws ExecutionNotFound String startJob(AuthContext auth, JobReference jobReference, String jobArgString, String jobFilter, String asUser) throws JobNotFound + Map deleteBulkExecutionIds(AuthContext auth, Collection ids, String asUser) + } \ No newline at end of file diff --git a/rundeckapp/grails-app/services/rundeck/services/jobs/ResolvedAuthJobService.groovy b/rundeckapp/grails-app/services/rundeck/services/jobs/ResolvedAuthJobService.groovy index f0e835a04d2..dce55b629cb 100644 --- a/rundeckapp/grails-app/services/rundeck/services/jobs/ResolvedAuthJobService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/jobs/ResolvedAuthJobService.groovy @@ -51,6 +51,11 @@ class ResolvedAuthJobService implements JobService { authJobService.getJobState(authContext, jobReference) } + List searchExecutions(String state, String project, String job, String excludeJob, + String since, boolean reverseSince){ + authJobService.searchExecutions(authContext, state, project, job, excludeJob, since, reverseSince) + } + List searchExecutions(String state, String project, String job, String excludeJob, String since){ authJobService.searchExecutions(authContext, state, project, job, excludeJob, since) } @@ -64,4 +69,8 @@ class ResolvedAuthJobService implements JobService { authJobService.startJob(authContext, jobReference, jobArgString, jobFilter, asUser) } + Map deleteBulkExecutionIds(Collection ids, String asUser){ + authJobService.deleteBulkExecutionIds(authContext, ids, asUser) + } + } From 282b27277cdff9b5d93f58e407b545aa8d42109b Mon Sep 17 00:00:00 2001 From: jtobard Date: Thu, 24 Aug 2017 11:32:21 -0300 Subject: [PATCH 2/3] query Executions expose to plugins --- .../dtolabs/rundeck/core/jobs/JobService.java | 7 ++ .../rundeck/services/FrameworkService.groovy | 10 ++ .../rundeck/services/JobStateService.groovy | 95 ++++++++++++++++++- .../jobs/AuthorizingJobService.groovy | 2 + .../jobs/ResolvedAuthJobService.groovy | 4 + 5 files changed, 116 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/dtolabs/rundeck/core/jobs/JobService.java b/core/src/main/java/com/dtolabs/rundeck/core/jobs/JobService.java index ff4fce7af5c..af3e9febc33 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/jobs/JobService.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/jobs/JobService.java @@ -122,4 +122,11 @@ List searchExecutions(String state, String project, String j * @return [success:true/false, failures:[ [success:false, message: String, id: id],... ], successTotal:Integer] */ Map deleteBulkExecutionIds(Collection ids, String asUser); + + /** + * + * @param filter for query executions + * @return map with results and total + */ + Map queryExecutions(Map filter); } diff --git a/rundeckapp/grails-app/services/rundeck/services/FrameworkService.groovy b/rundeckapp/grails-app/services/rundeck/services/FrameworkService.groovy index 4eadde08b65..8520b4eb0a6 100644 --- a/rundeckapp/grails-app/services/rundeck/services/FrameworkService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/FrameworkService.groovy @@ -16,6 +16,7 @@ package rundeck.services +import com.dtolabs.rundeck.app.support.ExecutionQuery import com.dtolabs.rundeck.core.authentication.Group import com.dtolabs.rundeck.core.authentication.Username import com.dtolabs.rundeck.core.authorization.Attribute @@ -1065,4 +1066,13 @@ class FrameworkService implements ApplicationContextAware { Map deleteBulkExecutionIds(Collection ids, AuthContext authContext, String username) { executionService.deleteBulkExecutionIds(ids,authContext,username) } + + /** + * non transactional interface to query executions + * {@link ExecutionService#queryExecutions queryExecutions} + * @return [result:result,total:total] + */ + Map queryExecutions(ExecutionQuery query, int offset=0, int max=-1) { + executionService.queryExecutions(query,offset,max) + } } diff --git a/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy b/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy index de6f41596ae..b348c5154a7 100644 --- a/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy @@ -17,6 +17,7 @@ package rundeck.services import com.dtolabs.rundeck.app.support.BaseNodeFilters +import com.dtolabs.rundeck.app.support.ExecutionQuery import com.dtolabs.rundeck.core.authorization.AuthContext import com.dtolabs.rundeck.core.authorization.UserAndRolesAuthContext import com.dtolabs.rundeck.core.common.Framework @@ -228,8 +229,11 @@ class JobStateService implements AuthorizingJobService { throw new ExecutionNotFound("Execution not found", id, project) } - JobReferenceImpl jobRef = new JobReferenceImpl(id: se.extid, jobName: se.jobName, groupPath: se.groupPath, - project: se.project) + JobReferenceImpl jobRef + if(se) { + jobRef = new JobReferenceImpl(id: se.extid, jobName: se.jobName, groupPath: se.groupPath, + project: se.project) + } new ExecutionReferenceImpl(id:exec.id, options: exec.argString, filter: exec.filter, job: jobRef, dateStarted: exec.dateStarted, status: exec.status, succeededNodeList: exec.succeededNodeList, failedNodeList: exec.failedNodeList) @@ -275,4 +279,91 @@ class JobStateService implements AuthorizingJobService { Map deleteBulkExecutionIds(AuthContext auth, Collection ids, String asUser){ frameworkService.deleteBulkExecutionIds(ids,auth, asUser) } + + @Override + Map queryExecutions(AuthContext auth,Map filter){ + ExecutionQuery query = new ExecutionQuery() + query.projFilter = filter.project + int offset = filter.offset?:0 + query.offset = offset + int max = filter.max?:0 + query.max = max + if(filter.recentFilter){ + query.recentFilter = filter.recentFilter + query.configureFilter() + } + if (filter.olderFilter) { + Date endDate=ExecutionQuery.parseRelativeDate(filter.olderFilter) + if(null!=endDate){ + query.endbeforeFilter = endDate + query.doendbeforeFilter = true + } + } + if(filter.statusFilter){ + query.statusFilter = filter.statusFilter + } + if(filter.userFilter){ + query.userFilter = filter.userFilter + } + if(filter.adhoc){ + query.adhoc = true + }else if(filter.jobonly){ + query.adhoc = false + } + if(filter.groupPath){ + query.groupPath = filter.groupPath + } + if(filter.groupPathExact){ + query.groupPathExact = filter.groupPathExact + } + if(filter.excludeGroupPath){ + query.excludeGroupPath = filter.excludeGroupPath + } + if(filter.excludeGroupPathExact){ + query.excludeGroupPathExact = filter.excludeGroupPathExact + } + if(filter.jobFilter){ + query.jobFilter = filter.jobFilter + } + if(filter.excludeJobFilter){ + query.excludeJobFilter = filter.excludeJobFilter + } + if(filter.excludeJobExactFilter){ + query.excludeJobExactFilter = filter.excludeJobExactFilter + } + if(filter.jobExactFilter){ + query.jobExactFilter = filter.jobExactFilter + } + if(filter.excludeJobIdListFilter){ + query.excludeJobIdListFilter = filter.excludeJobIdListFilter.tokenize(',') + } + if(filter.excludeJobListFilter){ + query.excludeJobListFilter = filter.excludeJobListFilter.tokenize(',') + } + if(filter.jobListFilter){ + query.jobListFilter = filter.jobListFilter.tokenize(',') + } + if(filter.jobIdListFilter){ + query.jobIdListFilter = filter.jobIdListFilter.tokenize(',') + } + def results = frameworkService.queryExecutions(query, offset, max) + def result=results.result + def total=results.total + //filter query results to READ authorized executions + def filtered = frameworkService.filterAuthorizedProjectExecutionsAll(auth,result,[AuthConstants.ACTION_READ]) + def idList = [] + filtered.each { Execution exec-> + ScheduledExecution se = exec.scheduledExecution + JobReferenceImpl jobRef + if(se){ + jobRef = new JobReferenceImpl(id: se.extid, jobName: se.jobName, groupPath: se.groupPath, + project: se.project) + } + ExecutionReferenceImpl execRef = new ExecutionReferenceImpl(id:exec.id, options: exec.argString, filter: exec.filter, job: jobRef, + dateStarted: exec.dateStarted, status: exec.status, succeededNodeList: exec.succeededNodeList, + failedNodeList: exec.failedNodeList) + idList.push(execRef) + } + return [result:idList, total:idList.size()] + } } diff --git a/rundeckapp/grails-app/services/rundeck/services/jobs/AuthorizingJobService.groovy b/rundeckapp/grails-app/services/rundeck/services/jobs/AuthorizingJobService.groovy index 2d1580c13fe..16bfd57773e 100644 --- a/rundeckapp/grails-app/services/rundeck/services/jobs/AuthorizingJobService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/jobs/AuthorizingJobService.groovy @@ -48,4 +48,6 @@ interface AuthorizingJobService { Map deleteBulkExecutionIds(AuthContext auth, Collection ids, String asUser) + Map queryExecutions(AuthContext auth,Map filter) + } \ No newline at end of file diff --git a/rundeckapp/grails-app/services/rundeck/services/jobs/ResolvedAuthJobService.groovy b/rundeckapp/grails-app/services/rundeck/services/jobs/ResolvedAuthJobService.groovy index dce55b629cb..f1e6cc51e70 100644 --- a/rundeckapp/grails-app/services/rundeck/services/jobs/ResolvedAuthJobService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/jobs/ResolvedAuthJobService.groovy @@ -73,4 +73,8 @@ class ResolvedAuthJobService implements JobService { authJobService.deleteBulkExecutionIds(authContext, ids, asUser) } + Map queryExecutions(Map filter){ + authJobService.queryExecutions(authContext, filter) + } + } From d630893938ec44978f55f610e98203749f4c076c Mon Sep 17 00:00:00 2001 From: jtobard Date: Thu, 24 Aug 2017 16:46:03 -0300 Subject: [PATCH 3/3] unit test, adhoc command for adhoc executions --- .../core/execution/ExecutionReference.java | 2 ++ .../rundeck/services/JobStateService.groovy | 12 ++++--- .../execution/ExecutionReferenceImpl.groovy | 2 ++ .../services/JobStateServiceSpec.groovy | 33 ++++++++++++++++++- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionReference.java b/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionReference.java index bb40687180e..1aa53a888a2 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionReference.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/execution/ExecutionReference.java @@ -11,10 +11,12 @@ public interface ExecutionReference { String getOptions(); JobReference getJob(); Date getDateStarted(); + Date getDateCompleted(); String getStatus(); String getSucceededNodeList(); String getFailedNodeList(); String getTargetNodes(); + String getAdhocCommand(); } diff --git a/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy b/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy index b348c5154a7..e3e2e435f9c 100644 --- a/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/JobStateService.groovy @@ -235,8 +235,8 @@ class JobStateService implements AuthorizingJobService { project: se.project) } new ExecutionReferenceImpl(id:exec.id, options: exec.argString, filter: exec.filter, job: jobRef, - dateStarted: exec.dateStarted, status: exec.status, succeededNodeList: exec.succeededNodeList, - failedNodeList: exec.failedNodeList) + dateStarted: exec.dateStarted, status: exec.status, succeededNodeList: exec.succeededNodeList, + dateCompleted:exec.dateCompleted, failedNodeList: exec.failedNodeList) } @@ -359,9 +359,13 @@ class JobStateService implements AuthorizingJobService { jobRef = new JobReferenceImpl(id: se.extid, jobName: se.jobName, groupPath: se.groupPath, project: se.project) } - ExecutionReferenceImpl execRef = new ExecutionReferenceImpl(id:exec.id, options: exec.argString, filter: exec.filter, job: jobRef, - dateStarted: exec.dateStarted, status: exec.status, succeededNodeList: exec.succeededNodeList, + ExecutionReferenceImpl execRef = new ExecutionReferenceImpl(id:exec.id, options: exec.argString, + filter: exec.filter, job: jobRef, dateStarted: exec.dateStarted, dateCompleted:exec.dateCompleted, + status: exec.status, succeededNodeList: exec.succeededNodeList, failedNodeList: exec.failedNodeList) + if(!se && exec.workflow && exec.workflow.commands && exec.workflow.commands[0]){ + execRef.adhocCommand = exec.workflow.commands[0].summarize() + } idList.push(execRef) } return [result:idList, total:idList.size()] diff --git a/rundeckapp/grails-app/services/rundeck/services/execution/ExecutionReferenceImpl.groovy b/rundeckapp/grails-app/services/rundeck/services/execution/ExecutionReferenceImpl.groovy index 3a1fce13a03..c67d381a648 100644 --- a/rundeckapp/grails-app/services/rundeck/services/execution/ExecutionReferenceImpl.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/execution/ExecutionReferenceImpl.groovy @@ -10,10 +10,12 @@ class ExecutionReferenceImpl implements ExecutionReference { String id JobReferenceImpl job Date dateStarted + Date dateCompleted String status String succeededNodeList String failedNodeList String targetNodes + String adhocCommand @Override String toString() { diff --git a/rundeckapp/test/unit/rundeck/services/JobStateServiceSpec.groovy b/rundeckapp/test/unit/rundeck/services/JobStateServiceSpec.groovy index 64d7d4a15a6..813b5d27d60 100644 --- a/rundeckapp/test/unit/rundeck/services/JobStateServiceSpec.groovy +++ b/rundeckapp/test/unit/rundeck/services/JobStateServiceSpec.groovy @@ -16,6 +16,8 @@ package rundeck.services +import com.dtolabs.rundeck.app.support.ExecutionQuery +import com.dtolabs.rundeck.core.authorization.AuthContext import com.dtolabs.rundeck.core.authorization.SubjectAuthContext import com.dtolabs.rundeck.core.dispatcher.ExecutionState import com.dtolabs.rundeck.core.execution.ExecutionNotFound @@ -495,7 +497,36 @@ class JobStateServiceSpec extends Specification { } - + def "queryExecutions simple params"() { + setup: + def auth = Mock(AuthContext) + service.frameworkService = Mock(FrameworkService) + def mockExec = Mock(Execution) + when: + def result = service.queryExecutions(auth, filter) + then: + result + result.total == expTotal + 1 * service.frameworkService.queryExecutions(_, 0, 0) >> {ExecutionQuery query,offset,max -> + if(query.adhoc){ + return [result: [mockExec], total: 1] + } + if(query.jobIdListFilter){ + return [result: [mockExec,mockExec], total: 2] + } + [result: [], total: 0] + } + 1 * service.frameworkService.filterAuthorizedProjectExecutionsAll(_, _, [AuthConstants.ACTION_READ]) >> + {authcontext, inputArr, actions -> + return inputArr + } + where: + filter | expTotal + [:] | 0 + [adhoc:true] | 1 + [jobonly:true] | 0 + [jobIdListFilter:'1,2'] | 2 + } def setTestExecutions(projectName, jobUuid){