Skip to content

Commit

Permalink
Refactor job service and provider
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseOrtiz committed May 10, 2024
1 parent 3fa97f9 commit 673356c
Show file tree
Hide file tree
Showing 7 changed files with 470 additions and 401 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3142,7 +3142,7 @@ Since: v17''',
}


def list = jobSchedulesService.getAllScheduled(uuid)
def list = jobSchedulesService.getAllScheduled(uuid, null)
//filter authorized jobs
Map<String, UserAndRolesAuthContext> projectAuths = [:]
def authForProject = { String project ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class JobSchedulesService implements SchedulesManager {
}

@Override
List getAllScheduled(String serverUUID = null, String project = null) {
List getAllScheduled(String serverUUID, String project) {
return rundeckJobSchedulesManager.getAllScheduled(serverUUID, project)
}

Expand Down
281 changes: 279 additions & 2 deletions rundeckapp/grails-app/services/rundeck/services/RdJobService.groovy
Original file line number Diff line number Diff line change
@@ -1,14 +1,63 @@
package rundeck.services

import com.dtolabs.rundeck.core.authorization.UserAndRolesAuthContext
import com.dtolabs.rundeck.core.common.INodeSet
import com.dtolabs.rundeck.core.jobs.JobLifecycleComponentException
import com.dtolabs.rundeck.core.jobs.JobOption
import com.dtolabs.rundeck.core.jobs.options.JobOptionConfigData
import com.dtolabs.rundeck.plugins.jobs.JobOptionImpl
import com.dtolabs.rundeck.plugins.jobs.JobPersistEventImpl
import grails.compiler.GrailsCompileStatic
import grails.events.annotation.Publisher
import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import groovy.util.logging.Log4j2
import org.rundeck.app.authorization.AppAuthContextProcessor
import org.rundeck.app.components.RundeckJobDefinitionManager
import org.rundeck.app.components.jobs.ImportedJob
import org.rundeck.app.data.job.converters.ScheduledExecutionToJobConverter
import org.rundeck.app.data.job.schedule.DefaultJobDataChangeDetector
import org.rundeck.app.data.model.v1.DeletionResult
import org.rundeck.app.data.model.v1.job.JobData
import org.rundeck.app.data.model.v1.job.component.JobComponentData
import org.rundeck.app.data.model.v1.query.JobQueryInputData
import org.rundeck.app.data.providers.v1.job.JobDataProvider
import org.rundeck.app.events.LogJobChangeEvent
import org.rundeck.app.job.component.JobComponentDataImportExport
import org.rundeck.app.jobs.options.JobOptionConfigPluginAttributes
import org.rundeck.core.auth.AuthConstants
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.context.request.RequestContextHolder
import rundeck.ScheduledExecution
import rundeck.data.job.RdJob
import rundeck.data.job.RdOption
import rundeck.data.validation.exception.DataValidationException
import rundeck.services.audit.JobUpdateAuditEvent
import rundeck.services.data.ScheduledExecutionDataService

@CompileStatic
import javax.servlet.http.HttpSession

@GrailsCompileStatic
@Log4j2
class RdJobService {

@Autowired
ScheduledExecutionDataService scheduledExecutionDataService
@Autowired
RundeckJobDefinitionManager rundeckJobDefinitionManager
@Autowired
FrameworkService frameworkService
@Autowired
JobSchedulesService jobSchedulesService
@Autowired
JobSchedulerService jobSchedulerService
@Autowired
AppAuthContextProcessor rundeckAuthContextProcessor
@Autowired
ScheduledExecutionService scheduledExecutionService
@Autowired
JobLifecycleComponentService jobLifecycleComponentService

JobDataProvider jobDataProvider

JobData getJobByIdOrUuid(Serializable id) {
Expand Down Expand Up @@ -37,8 +86,36 @@ class RdJobService {
return jobDataProvider.findByUuid(uuid)
}

@CompileStatic(TypeCheckingMode.SKIP)
JobData saveJob(JobData job) {
return jobDataProvider.save(job)
def session = getSession()
def authCtx = frameworkService.userAuthContext(session) as UserAndRolesAuthContext
ScheduledExecution se = job.uuid ? scheduledExecutionDataService.findByUuid(job.uuid) : null
boolean isnew = false
if(!se) {
isnew = true
se = new ScheduledExecution()
}
RdJob rdJob = (RdJob)job
if(!rdJob.validate()) {
throw new DataValidationException(rdJob)
}
LogJobChangeEvent logEvent = new LogJobChangeEvent(isnew ? 'create' : 'update','GormJobDataProvider.save', session.user)
JobChangeData jobChangeData = detectJobChanges(se, rdJob, logEvent)
runComponentBeforeSave(authCtx.username, rdJob)
se.project = rdJob.project
authorizeEditAndUpdateJobUserAndRoles(authCtx, se, rdJob)
def importedJob = validateComponents(se, rdJob)
if(rdJob.errors.hasErrors()) throw new DataValidationException(rdJob)

rundeckJobDefinitionManager.persistComponents(importedJob,authCtx)
def saved = jobDataProvider.save(se)
def savedSe = scheduledExecutionDataService.findByUuid(saved.uuid)
rundeckJobDefinitionManager.waspersisted(importedJob, authCtx)
rescheduleJob(savedSe, jobChangeData)
publishJobUpdateAuditEvent(savedSe, isnew)
publishLogJobChangeEvent(savedSe, logEvent)
return ScheduledExecutionToJobConverter.convert(savedSe)
}

DeletionResult delete(String id) {
Expand All @@ -51,4 +128,204 @@ class RdJobService {
def listJobs(JobQueryInputData jobQueryInputData) {
jobDataProvider.queryJobs(jobQueryInputData)
}

@CompileStatic(TypeCheckingMode.SKIP)
def getSession() {
RequestContextHolder.currentRequestAttributes().getSession()
}

@Publisher('log.job.change.event')
LogJobChangeEvent publishLogJobChangeEvent(ScheduledExecution se, LogJobChangeEvent event) {
event.jobData = se
return event
}

@Publisher('audit.job.update')
JobUpdateAuditEvent publishJobUpdateAuditEvent(ScheduledExecution se, boolean isnew) {
return new JobUpdateAuditEvent(jobUuid: se.uuid, project: se.project, fullName: se.generateFullName(), isnew: isnew)
}

void runComponentBeforeSave(String username, RdJob rdJob) {
INodeSet nodeSet = scheduledExecutionService.getNodes(rdJob,null)
JobPersistEventImpl jobPersistEvent = new JobPersistEventImpl(
rdJob.jobName,
rdJob.project,
convertToJobOptions(rdJob.optionSet),
nodeSet,
username,
rdJob.nodeConfig.filter
)
def jobEventStatus
try {
jobEventStatus = jobLifecycleComponentService?.beforeJobSave(rdJob.project, jobPersistEvent)
} catch (JobLifecycleComponentException exception) {
exception.printStackTrace()
log.debug("JobLifecycle error: " + exception.message, exception)
rdJob.errors.reject(
'scheduledExecution.plugin.error.message',
['Job Lifecycle: ' + exception.message].toArray(),
"A Plugin returned an error: " + exception.message
)
return
}
if(jobEventStatus?.isUseNewValues()) {
rdJob.optionSet = jobEventStatus.getOptions()
.collect {opt -> RdOption.convertFromJobOption(opt, rdJob.optionSet?.find { it.name == opt.name })} as SortedSet<RdOption>
rdJob.validate()
}
}

ImportedJob<ScheduledExecution> validateComponents(ScheduledExecution se, RdJob rdJob) {
def associations = [:] as Map<String, Object>
rundeckJobDefinitionManager.jobDefinitionComponents.each{ k, val ->
if(!(val instanceof JobComponentDataImportExport)) {
log.warn("Job component {} cannot be imported to the job data because no importer is defined", k)
return
}
JobComponentDataImportExport importer = (JobComponentDataImportExport)val
if(!rdJob.components.containsKey(importer.componentKey)) return
def data = importer.importFromJobData(rdJob)
if(data) associations[k] = data
}
def importedJob = RundeckJobDefinitionManager.importedJob(se,associations)
def updatedJob = rundeckJobDefinitionManager.updateJob(se, importedJob, [:])
validateComponentsExist(rdJob)
def rptSet = rundeckJobDefinitionManager.validateImportedJob(updatedJob)
rptSet.validations.each { componentName, rpt ->
rpt.errors.each { k, v ->
rdJob.errors.rejectValue("components",
"jobData.components.invalidconfiguration",
[componentName, k, v] as Object[],
'Job Component: {0} invalid config: {1} : {2}')
}
}
return updatedJob
}

void validateComponentsExist(RdJob rdJob) {
def jobDefinitionComponentKeys = rundeckJobDefinitionManager.jobDefinitionComponents.values().findAll{it instanceof JobComponentDataImportExport }.collect { ((JobComponentDataImportExport)it).componentKey }
rdJob.components.each { String componentName, JobComponentData value ->
if (!jobDefinitionComponentKeys.contains(componentName)) {
rdJob.errors.rejectValue("components",
"jobData.components.notfound",
[componentName] as Object[],
'Job Component of type: {0} could not be found')
}
}
}

JobChangeData detectJobChanges(ScheduledExecution se, RdJob rdJob, LogJobChangeEvent logEvent) {
JobChangeData jobChangeData = new JobChangeData()
if(!se.id) return jobChangeData
String oldjobname = se.generateJobScheduledName()
String oldjobgroup = se.generateJobGroupName()
jobChangeData.isScheduled = jobSchedulesService.isScheduled(se.uuid)
DefaultJobDataChangeDetector detector = new DefaultJobDataChangeDetector(
localScheduled: se.scheduled,
originalCron: se.generateCrontabExression(),
originalSchedule: se.scheduleEnabled,
originalExecution: se.executionEnabled,
originalTz: se.timeZone,
originalRef: new JobRevReferenceImpl(
id: se.extid,
jobName: se.jobName,
groupPath: se.groupPath,
project: se.project,
version: se.version
)
)
jobChangeData.renamed = detector.wasRenamed(rdJob)

if(jobChangeData.renamed){
logEvent.changeinfo.rename = true
logEvent.changeinfo.origName = oldjobname
logEvent.changeinfo.origGroup = oldjobgroup
jobChangeData.scheduledJobName = oldjobname
jobChangeData.scheduledGroupPath = oldjobgroup
}

boolean schedulingWasChanged = detector.schedulingWasChanged(se)
if(frameworkService.isClusterModeEnabled()){
if (schedulingWasChanged) {
JobReferenceImpl jobReference = se.asReference() as JobReferenceImpl
jobReference.setOriginalQuartzJobName(oldjobname)
jobReference.setOriginalQuartzGroupName(oldjobgroup)
jobChangeData.scheduleOwnerModified = jobSchedulerService.updateScheduleOwner(jobReference)
if (jobChangeData.scheduleOwnerModified) {
rdJob.serverNodeUUID = frameworkService.serverUUID
}
}
if (!rdJob.serverNodeUUID) {
rdJob.serverNodeUUID = frameworkService.serverUUID
}
}

jobChangeData
}

@CompileStatic(TypeCheckingMode.SKIP)
void authorizeEditAndUpdateJobUserAndRoles(UserAndRolesAuthContext authContext, ScheduledExecution se, RdJob rdJob) {
def authAction = se.id ? AuthConstants.ACTION_UPDATE : AuthConstants.ACTION_CREATE
se.user = authContext.username
se.userRoles = authContext.roles as List<String>
if (!rundeckAuthContextProcessor.authorizeProjectJobAll(authContext, se, [authAction], se.project)) {
rdJob.errors.rejectValue('jobName', 'ScheduledExecution.jobName.unauthorized', [authAction, rdJob.jobName].toArray(), 'Unauthorized action: {0} for value: {1}')
rdJob.errors.rejectValue('groupPath', 'ScheduledExecution.groupPath.unauthorized', [ authAction, rdJob.groupPath].toArray(), 'Unauthorized action: {0} for value: {1}')
return
}
}

void rescheduleJob(ScheduledExecution se, JobChangeData jobChangeData) {
scheduledExecutionService.rescheduleJob(se, jobChangeData.isScheduled,
jobChangeData.scheduledJobName, jobChangeData.scheduledGroupPath, false,
!jobChangeData.schedulingWasChanged || !jobChangeData.scheduleOwnerModified)
}

SortedSet<JobOption> convertToJobOptions(SortedSet<RdOption> rdOptions) {
def opts = new TreeSet<JobOption>()
if(!rdOptions) return opts

opts.addAll(rdOptions.collect {opt ->
JobOptionConfigData jobOptionConfigData= new JobOptionConfigData()
jobOptionConfigData.addConfig(new JobOptionConfigPluginAttributes(opt.configMap))

JobOptionImpl.builder()
.name(opt.name)
.description(opt.description)
.defaultValue(opt.defaultValue)
.delimiter(opt.delimiter)
.defaultStoragePath(opt.defaultStoragePath)
.isDate(opt.isDate)
.dateFormat(opt.dateFormat)
.regex(opt.regex)
.enforced(opt.enforced)
.hidden(opt.hidden)
.optionType(opt.optionType)
.label(opt.label)
.required(opt.required)
.realValuesUrl(opt.realValuesUrl)
.sortIndex(opt.sortIndex)
.optionValues(opt.optionValues)
.optionValuesPluginType(opt.optionValuesPluginType)
.secureExposed(opt.secureExposed)
.secureInput(opt.secureInput)
.configData(jobOptionConfigData)
.multivalueAllSelected(opt.multivalueAllSelected)
.multivalued(opt.multivalued)
.sortValues(opt.sortValues)
.valuesListDelimiter(opt.valuesListDelimiter)
.valuesList(opt.valuesList)
.build()
})
opts
}

static class JobChangeData {
String scheduledJobName
String scheduledGroupPath
boolean isScheduled
boolean renamed
boolean scheduleOwnerModified
boolean schedulingWasChanged
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ class ScheduledExecutionService implements ApplicationContextAware, Initializing
UserDataProvider userDataProvider
JobDataProvider jobDataProvider
UserService userService
RdJobService rdJobService

@Override
void afterPropertiesSet() throws Exception {
Expand All @@ -235,7 +236,7 @@ class ScheduledExecutionService implements ApplicationContextAware, Initializing
Map<String, String> getPropertiesMapping() { ConfigPropertiesMapping }

JobData saveJob(JobData job) {
jobDataProvider.save(job)
rdJobService.saveJob(job)
}
/**
* Return project config for node cache delay
Expand Down Expand Up @@ -808,7 +809,7 @@ class ScheduledExecutionService implements ApplicationContextAware, Initializing
* @param serverUUID
*/
def unscheduleJobs(String serverUUID=null){
def schedJobs = serverUUID ? jobSchedulesService.getAllScheduled(serverUUID) : jobSchedulesService.getAllScheduled()
def schedJobs = jobSchedulesService.getAllScheduled(serverUUID, null)
schedJobs.each { ScheduledExecution se ->
def jobname = se.generateJobScheduledName()
def groupname = se.generateJobGroupName()
Expand Down

0 comments on commit 673356c

Please sign in to comment.