Skip to content

Commit

Permalink
Initial multivalued options [rundeck#220]
Browse files Browse the repository at this point in the history
  • Loading branch information
gschueler committed Mar 2, 2011
2 parents e2baa80 + cb56177 commit 0efa33f
Show file tree
Hide file tree
Showing 21 changed files with 844 additions and 162 deletions.
8 changes: 8 additions & 0 deletions docs/en/05-job-options/05-chapter1.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ Identification
: Here you provide the option's name and description. The name
becomes part of acceptable arguments to the Job while the
description will be provided as help text to users running the Job.

The Default Value will be pre-selected in the GUI when the option is presented.

Allowed values

Expand All @@ -124,6 +126,12 @@ Requirement
: Indicates if the Job can only run if a choice is provided for
that Option. Choosing "No" states the option is not required
Choose "Yes" to state the option is required.

If a Default Value is set for the option, then this value will automatically be set for the option if it is Required, even if not specified among the arguments when executing a job via the command-line or API.

Multi-valued

: Defines if the user input can consist of multiple values. Choosing "No" states that only a single value can chosen as input. Choosing "Yes" states that the user may select multiple input values from the Allowed values and/or enter multiple values of their own. The delimiter string will be used to separate the multiple values when the Job is run.

Once satisfied with the option definition, press the "Save" button to
add it to the Job definition. Pressing the "Cancel" button will
Expand Down
20 changes: 18 additions & 2 deletions docs/en/manpages/man5/job-v20.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,28 @@ required

: Boolean specifying that the option is required

multivalued

: "true/false" - whether the option supports multiple input values

delimiter

: A string used to conjoin multiple input values. (Required if `multivalued` is "true")


*Example*

Define defaults for the "port" option.
Define defaults for the "port" option, requiring regex match.

<option name="port" value="80" values="80,8080,8888" regex="\d+"/>

Define defaults for the "port" option, enforcing the values list.

<option name="port" value="80" values="80,8080,8888" enforcedvalues="true" />

Define defaults for the "ports" option, allowing multple values separated by ",".

<option name="port" value="80" values="80,8080,8888" enforcedvalues="true" regex="\d+"/>
<option name="port" value="80" values="80,8080,8888" enforcedvalues="true" multivalued="true" delimiter="," />


#### valuesUrl JSON
Expand Down
10 changes: 10 additions & 0 deletions docs/en/manpages/man5/job-yaml-v12.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,14 @@ Optional map entries are:

: A URL to an endpoint that will return a JSON-formatted set of values for the option.

`multivalued`

: "true/false" - whether the option supports multiple input values

`delimiter`

: A string used to conjoin multiple input values. (Required if `multivalued` is "true")

Example:

test:
Expand All @@ -263,6 +271,8 @@ Example:
- avalue
- bvalue
- cvalue
multivalued: true
delimiter: ','

#### valuesUrl JSON

Expand Down
6 changes: 6 additions & 0 deletions rundeckapp/grails-app/controllers/EditOptsController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ class EditOptsController {
}
}
}
if(opt.multivalued && !opt.delimiter){
opt.errors.rejectValue('delimiter', 'option.delimiter.blank.message')
}
return result
}

Expand Down Expand Up @@ -351,6 +354,9 @@ class EditOptsController {
if(null==params.required){
params.required=false
}
if(null==params.multivalued){
params.multivalued=false
}
opt.properties = params
if(params.valuesType == 'list'){
opt.valuesUrl=null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,8 @@ class ScheduledExecutionController {
def oldjobname = scheduledExecution.generateJobScheduledName()
def oldjobgroup = scheduledExecution.generateJobGroupName()
def oldsched = scheduledExecution.scheduled
def optparams = params.findAll { it.key.startsWith("command.option.")}
def nonopts = params.findAll { !it.key.startsWith("command.option.") && it.key!='workflow' && it.key!='options'&& it.key!='notifications'}
def optparams = params.findAll { it.key.startsWith("option.")}
def nonopts = params.findAll { !it.key.startsWith("option.") && it.key!='workflow' && it.key!='options'&& it.key!='notifications'}
scheduledExecution.properties = nonopts

final Map oldopts = params.findAll{it.key=~/^(name|command|type|adhocExecution|adhocFilepath|adhoc.*String)$/}
Expand Down Expand Up @@ -739,7 +739,8 @@ class ScheduledExecutionController {
def Map optdefparams=params.options["options[${i}]"]
def Option theopt = new Option(optdefparams)
scheduledExecution.addToOptions(theopt)
if (!theopt.validate()) {
EditOptsController._validateOption(theopt)
if (theopt.errors.hasErrors() || !theopt.validate() ) {
failed = true
theopt.discard()
def errmsg = optdefparams.name + ": " + theopt.errors.allErrors.collect {g.message(error: it)}.join(";")
Expand Down Expand Up @@ -968,10 +969,11 @@ class ScheduledExecutionController {
def i=0;
params.options.each{Option theopt->
scheduledExecution.addToOptions(theopt)
if (!theopt.validate()) {
EditOptsController._validateOption(theopt)
if (theopt.errors.hasErrors()|| !theopt.validate()) {
failed = true
theopt.discard()
def errmsg = optdefparams.name + ": " + theopt.errors.allErrors.collect {g.message(error: it)}.join(";")
def errmsg = theopt.name + ": " + theopt.errors.allErrors.collect {g.message(error: it)}.join(";")
scheduledExecution.errors.rejectValue(
'options',
'scheduledExecution.options.invalid.message',
Expand Down Expand Up @@ -1425,8 +1427,8 @@ class ScheduledExecutionController {
def rolelist = (session?.roles) ? session.roles : []
boolean failed=false;
def scheduledExecution = new ScheduledExecution()
def optparams = params.findAll {it.key.startsWith("command.option.")}
final Map nonopts = params.findAll {!it.key.startsWith("command.option.") && it.key != 'workflow'&& it.key != 'options'&& it.key != 'notifications'}
def optparams = params.findAll {it.key.startsWith("option.")}
final Map nonopts = params.findAll {!it.key.startsWith("option.") && it.key != 'workflow'&& it.key != 'options'&& it.key != 'notifications'}
final Map oldopts = params.findAll{it.key=~/^(name|command|type|adhocExecution|adhocFilepath|adhoc.*String)$/}
scheduledExecution.properties = nonopts
if(oldopts && !params.workflow){
Expand Down Expand Up @@ -1630,8 +1632,9 @@ class ScheduledExecutionController {
params.options.each{ origopt->
def Option theopt = origopt.createClone()
scheduledExecution.addToOptions(theopt)
theopt.scheduledExecution = scheduledExecution
if (!theopt.validate()) {
EditOptsController._validateOption(theopt)

if (theopt.errors.hasErrors() || !theopt.validate()) {
failed = true
theopt.discard()
def errmsg = optdefparams.name + ": " + theopt.errors.allErrors.collect {g.message(error: it)}.join(";")
Expand All @@ -1649,7 +1652,8 @@ class ScheduledExecutionController {
def Map optdefparams=params.options["options[${i}]"]
def Option theopt = new Option(optdefparams)
scheduledExecution.addToOptions(theopt)
if (!theopt.validate()) {
EditOptsController._validateOption(theopt)
if (theopt.errors.hasErrors() || !theopt.validate()) {
failed = true
theopt.discard()
def errmsg = optdefparams.name + ": " + theopt.errors.allErrors.collect {g.message(error: it)}.join(";")
Expand Down Expand Up @@ -2271,19 +2275,11 @@ class ScheduledExecutionController {
return [error:'unauthorized',message:msg]
}

def extra = [:]
def extra = params.extra

params.each{ key, value ->
def matcher= key =~ /^extra\.(.*)$/
if(matcher.matches()){
extra[matcher.group(1)]=value
}
}
def Execution e
def eid
try{
e= executionService.createExecution(scheduledExecution,framework,params.user,extra)
eid=scheduledExecutionService.scheduleTempJob(scheduledExecution,params.user,rolelist,e);
def Execution e= executionService.createExecution(scheduledExecution,framework,params.user,extra)
def eid=scheduledExecutionService.scheduleTempJob(scheduledExecution,params.user,rolelist,e);
return [executionId:eid,name:scheduledExecution.jobName, execution:e]
}catch(ExecutionServiceValidationException exc){
return [error:'invalid',message:exc.getMessage(),options:exc.getOptions(),errors:exc.getErrors()]
Expand Down Expand Up @@ -2496,18 +2492,18 @@ class ScheduledExecutionController {
return chain(controller: 'api', action: 'renderError')
}
Framework framework = frameworkService.getFrameworkFromUserSession(session, request)
def inparams = [:]
def inparams = [extra:[:]]
inparams["user"] = (session?.user) ? session.user : "anonymous"
def rolelist = (session?.roles) ? session.roles : []

if (params.argString) {
inparams["extra.argString"] = params.argString
inparams.extra["argString"] = params.argString
}
//convert api parameters to node filter parameters
def filters = FrameworkController.extractApiNodeFilterParams(params)
if (filters) {
filters.each {k, v ->
inparams['extra.' + k] = v
inparams.extra[k] = v
}
}

Expand Down
18 changes: 17 additions & 1 deletion rundeckapp/grails-app/domain/Option.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public class Option implements Comparable{
String valuesUrlString
String regex
String valuesList
Boolean multivalued
String delimiter
static belongsTo=[scheduledExecution:ScheduledExecution]
static transients=['valuesList','valuesUrlString']

Expand All @@ -58,6 +60,8 @@ public class Option implements Comparable{
valuesUrl(nullable:true)
regex(nullable:true)
scheduledExecution(nullable:true)
delimiter(nullable:true)
multivalued(nullable:true)
}

/**
Expand Down Expand Up @@ -87,6 +91,10 @@ public class Option implements Comparable{
if(values){
map.values=values as List
}
if(multivalued){
map.multivalued=multivalued
map.delimiter=delimiter?:','
}
return map
}

Expand All @@ -110,6 +118,12 @@ public class Option implements Comparable{
if(data.values){
opt.values=data.values instanceof Collection?new TreeSet(data.values):new TreeSet([data.values])
}
if(data.multivalued){
opt.multivalued=true
if(data.delimiter){
opt.delimiter=data.delimiter
}
}
return opt
}
/**
Expand Down Expand Up @@ -145,7 +159,7 @@ public class Option implements Comparable{
*/
public Option createClone(){
Option opt = new Option()
['name','description','defaultValue','enforced','required','values','valuesList','valuesUrl','regex'].each{k->
['name','description','defaultValue','enforced','required','values','valuesList','valuesUrl','regex','multivalued','delimiter'].each{k->
opt[k]=this[k]
}
if(!opt.valuesList && values){
Expand All @@ -164,6 +178,8 @@ public class Option implements Comparable{
", values=" + values +
", valuesUrl=" + valuesUrl +
", regex='" + regex + '\'' +
", multivalued='" + multivalued + '\'' +
", delimiter='" + delimiter + '\'' +
'}' ;
}

Expand Down
5 changes: 5 additions & 0 deletions rundeckapp/grails-app/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ option.regex.invalid.message=Invalid Regular Expression: {0}
option.values.regexmismatch.message=Allowed value "{0}" does not match the regex: {1}
option.enforced.emptyvalues.message=Allowed values (list or remote URL) must be specified if values are enforced
option.defaultValue.regexmismatch.message=Default value "{0}" does not match the regex: {1}
option.delimiter.blank.message=You must specify a delimiter for multivalued options
option.name.duplicate.message=Option with name "{0}" already exists
notexist.job.run=Job cannot be run because the object or project is not deployed.
unauthorized.job.run=You are not authorized to run this Job.
Expand Down Expand Up @@ -245,6 +246,10 @@ execution.show.mode.Tail.desc=View the last lines of the output file
execution.show.mode.Annotated.desc=View all output in grouped contexts sequentially
execution.show.mode.Compact.desc=View all output collated by node

#form text
form.option.multivalued.description=Allow multiple input values to be chosen.
form.option.delimiter.description=Delimiter will be used to join all input values.

# API Messages
api.error.api-version.required=API Version not specified
api.error.api-version.unsupported=Unsupported API Version "{0}". API Request: {1}. Reason: {2}
Expand Down
Loading

0 comments on commit 0efa33f

Please sign in to comment.