terraform/vendor/github.com/apparentlymart/go-rundeck-api/rundeck/job.go

404 lines
13 KiB
Go

package rundeck
import (
"encoding/xml"
"fmt"
"strings"
)
// JobSummary is an abbreviated description of a job that includes only its basic
// descriptive information and identifiers.
type JobSummary struct {
XMLName xml.Name `xml:"job"`
ID string `xml:"id,attr"`
Name string `xml:"name"`
GroupName string `xml:"group"`
ProjectName string `xml:"project"`
Description string `xml:"description,omitempty"`
}
type jobSummaryList struct {
XMLName xml.Name `xml:"jobs"`
Jobs []JobSummary `xml:"job"`
}
// JobDetail is a comprehensive description of a job, including its entire definition.
type JobDetail struct {
XMLName xml.Name `xml:"job"`
ID string `xml:"uuid,omitempty"`
Name string `xml:"name"`
GroupName string `xml:"group,omitempty"`
ProjectName string `xml:"context>project,omitempty"`
OptionsConfig *JobOptions `xml:"context>options,omitempty"`
Description string `xml:"description"`
LogLevel string `xml:"loglevel,omitempty"`
AllowConcurrentExecutions bool `xml:"multipleExecutions,omitempty"`
Dispatch *JobDispatch `xml:"dispatch,omitempty"`
CommandSequence *JobCommandSequence `xml:"sequence,omitempty"`
Timeout string `xml:"timeout,omitempty"`
Retry string `xml:"retry,omitempty"`
NodeFilter *JobNodeFilter `xml:"nodefilters,omitempty"`
/* If Dispatch is enabled, nodesSelectedByDefault is always present with true/false.
* by this reason omitempty cannot be present.
* This has to be handle by the user.
*/
NodesSelectedByDefault bool `xml:"nodesSelectedByDefault"`
Schedule *JobSchedule `xml:"schedule"`
}
type JobSchedule struct {
XMLName xml.Name `xml:"schedule"`
DayOfMonth *JobScheduleDayOfMonth `xml:"dayofmonth,omitempty"`
Time JobScheduleTime `xml:"time"`
Month JobScheduleMonth `xml:"month"`
WeekDay *JobScheduleWeekDay `xml:"weekday,omitempty"`
Year JobScheduleYear `xml:"year"`
}
type JobScheduleDayOfMonth struct {
XMLName xml.Name `xml:"dayofmonth"`
}
type JobScheduleMonth struct {
XMLName xml.Name `xml:"month"`
Day string `xml:"day,attr,omitempty"`
Month string `xml:"month,attr"`
}
type JobScheduleYear struct {
XMLName xml.Name `xml:"year"`
Year string `xml:"year,attr"`
}
type JobScheduleWeekDay struct {
XMLName xml.Name `xml:"weekday"`
Day string `xml:"day,attr"`
}
type JobScheduleTime struct {
XMLName xml.Name `xml:"time"`
Hour string `xml:"hour,attr"`
Minute string `xml:"minute,attr"`
Seconds string `xml:"seconds,attr"`
}
type jobDetailList struct {
XMLName xml.Name `xml:"joblist"`
Jobs []JobDetail `xml:"job"`
}
// JobOptions represents the set of options on a job, if any.
type JobOptions struct {
PreserveOrder bool `xml:"preserveOrder,attr,omitempty"`
Options []JobOption `xml:"option"`
}
// JobOption represents a single option on a job.
type JobOption struct {
XMLName xml.Name `xml:"option"`
// If AllowsMultipleChoices is set, the string that will be used to delimit the multiple
// chosen options.
MultiValueDelimiter string `xml:"delimiter,attr,omitempty"`
// If set, Rundeck will reject values that are not in the set of predefined choices.
RequirePredefinedChoice bool `xml:"enforcedvalues,attr,omitempty"`
// When either ValueChoices or ValueChoicesURL is set, controls whether more than one
// choice may be selected as the value.
AllowsMultipleValues bool `xml:"multivalued,attr,omitempty"`
// The name of the option, which can be used to interpolate its value
// into job commands.
Name string `xml:"name,attr,omitempty"`
// Regular expression to be used to validate the option value.
ValidationRegex string `xml:"regex,attr,omitempty"`
// If set, Rundeck requires a value to be set for this option.
IsRequired bool `xml:"required,attr,omitempty"`
// If set, the input for this field will be obscured in the UI. Useful for passwords
// and other secrets.
ObscureInput bool `xml:"secure,attr,omitempty"`
// If ObscureInput is set, StoragePath can be used to point out credentials.
StoragePath string `xml:"storagePath,attr,omitempty"`
// The default value of the option.
DefaultValue string `xml:"value,attr,omitempty"`
// If set, the value can be accessed from scripts.
ValueIsExposedToScripts bool `xml:"valueExposed,attr,omitempty"`
// A sequence of predefined choices for this option. Mutually exclusive with ValueChoicesURL.
ValueChoices JobValueChoices `xml:"values,attr"`
// A URL from which the predefined choices for this option will be retrieved.
// Mutually exclusive with ValueChoices
ValueChoicesURL string `xml:"valuesUrl,attr,omitempty"`
// Description of the value to be shown in the Rundeck UI.
Description string `xml:"description,omitempty"`
}
// JobValueChoices is a specialization of []string representing a sequence of predefined values
// for a job option.
type JobValueChoices []string
// JobCommandSequence describes the sequence of operations that a job will perform.
type JobCommandSequence struct {
XMLName xml.Name `xml:"sequence"`
// If set, Rundeck will continue with subsequent commands after a command fails.
ContinueOnError bool `xml:"keepgoing,attr"`
// Chooses the strategy by which Rundeck will execute commands. Can either be "node-first" or
// "step-first".
OrderingStrategy string `xml:"strategy,attr,omitempty"`
// Sequence of commands to run in the sequence.
Commands []JobCommand `xml:"command"`
// Description
Description string `xml:"description,omitempty"`
}
// JobCommand describes a particular command to run within the sequence of commands on a job.
// The members of this struct are mutually-exclusive except for the pair of ScriptFile and
// ScriptFileArgs.
type JobCommand struct {
XMLName xml.Name
// If the Workflow keepgoing is false, this allows the Workflow to continue when the Error Handler is successful.
ContinueOnError bool `xml:"keepgoingOnSuccess,attr,omitempty"`
// Description
Description string `xml:"description,omitempty"`
// On error:
ErrorHandler *JobCommand `xml:"errorhandler,omitempty"`
// A literal shell command to run.
ShellCommand string `xml:"exec,omitempty"`
// Add extension to the temporary filename.
FileExtension string `xml:"fileExtension,omitempty"`
// An inline program to run. This will be written to disk and executed, so if it is
// a shell script it should have an appropriate #! line.
Script string `xml:"script,omitempty"`
// A pre-existing file (on the target nodes) that will be executed.
ScriptFile string `xml:"scriptfile,omitempty"`
// When ScriptFile is set, the arguments to provide to the script when executing it.
ScriptFileArgs string `xml:"scriptargs,omitempty"`
// ScriptInterpreter is used to execute (Script)File with.
ScriptInterpreter *JobCommandScriptInterpreter `xml:"scriptinterpreter,omitempty"`
// A reference to another job to run as this command.
Job *JobCommandJobRef `xml:"jobref"`
// Configuration for a step plugin to run as this command.
StepPlugin *JobPlugin `xml:"step-plugin"`
// Configuration for a node step plugin to run as this command.
NodeStepPlugin *JobPlugin `xml:"node-step-plugin"`
}
// (Inline) Script interpreter
type JobCommandScriptInterpreter struct {
XMLName xml.Name `xml:"scriptinterpreter"`
InvocationString string `xml:",chardata"`
ArgsQuoted bool `xml:"argsquoted,attr,omitempty"`
}
// JobCommandJobRef is a reference to another job that will run as one of the commands of a job.
type JobCommandJobRef struct {
XMLName xml.Name `xml:"jobref"`
Name string `xml:"name,attr"`
GroupName string `xml:"group,attr"`
RunForEachNode bool `xml:"nodeStep,attr"`
NodeFilter *JobNodeFilter `xml:"nodefilters,omitempty"`
Arguments JobCommandJobRefArguments `xml:"arg"`
}
// JobCommandJobRefArguments is a string representing the arguments in a JobCommandJobRef.
type JobCommandJobRefArguments string
// JobPlugin is a configuration for a plugin to run within a job.
type JobPlugin struct {
XMLName xml.Name
Type string `xml:"type,attr"`
Config JobPluginConfig `xml:"configuration"`
}
// JobPluginConfig is a specialization of map[string]string for job plugin configuration.
type JobPluginConfig map[string]string
// JobNodeFilter describes which nodes from the project's resource list will run the configured
// commands.
type JobNodeFilter struct {
ExcludePrecedence bool `xml:"excludeprecedence"`
Query string `xml:"filter,omitempty"`
}
type jobImportResults struct {
Succeeded jobImportResultsCategory `xml:"succeeded"`
Failed jobImportResultsCategory `xml:"failed"`
Skipped jobImportResultsCategory `xml:"skipped"`
}
type jobImportResultsCategory struct {
Count int `xml:"count,attr"`
Results []jobImportResult `xml:"job"`
}
type jobImportResult struct {
ID string `xml:"id,omitempty"`
Name string `xml:"name"`
GroupName string `xml:"group,omitempty"`
ProjectName string `xml:"context>project,omitempty"`
Error string `xml:"error"`
}
type JobDispatch struct {
MaxThreadCount int `xml:"threadcount,omitempty"`
ContinueOnError bool `xml:"keepgoing"`
RankAttribute string `xml:"rankAttribute,omitempty"`
RankOrder string `xml:"rankOrder,omitempty"`
}
// GetJobSummariesForProject returns summaries of the jobs belonging to the named project.
func (c *Client) GetJobSummariesForProject(projectName string) ([]JobSummary, error) {
jobList := &jobSummaryList{}
err := c.get([]string{"project", projectName, "jobs"}, nil, jobList)
return jobList.Jobs, err
}
// GetJobsForProject returns the full job details of the jobs belonging to the named project.
func (c *Client) GetJobsForProject(projectName string) ([]JobDetail, error) {
jobList := &jobDetailList{}
err := c.get([]string{"jobs", "export"}, map[string]string{"project": projectName}, jobList)
if err != nil {
return nil, err
}
return jobList.Jobs, nil
}
// GetJob returns the full job details of the job with the given id.
func (c *Client) GetJob(id string) (*JobDetail, error) {
jobList := &jobDetailList{}
err := c.get([]string{"job", id}, nil, jobList)
if err != nil {
return nil, err
}
return &jobList.Jobs[0], nil
}
// CreateJob creates a new job based on the provided structure.
func (c *Client) CreateJob(job *JobDetail) (*JobSummary, error) {
return c.importJob(job, "create")
}
// CreateOrUpdateJob takes a job detail structure which has its ID set and either updates
// an existing job with the same id or creates a new job with that id.
func (c *Client) CreateOrUpdateJob(job *JobDetail) (*JobSummary, error) {
return c.importJob(job, "update")
}
func (c *Client) importJob(job *JobDetail, dupeOption string) (*JobSummary, error) {
jobList := &jobDetailList{
Jobs: []JobDetail{*job},
}
args := map[string]string{
"format": "xml",
"dupeOption": dupeOption,
"uuidOption": "preserve",
}
result := &jobImportResults{}
err := c.postXMLBatch([]string{"jobs", "import"}, args, jobList, result)
if err != nil {
return nil, err
}
if result.Failed.Count > 0 {
errMsg := result.Failed.Results[0].Error
return nil, fmt.Errorf(errMsg)
}
if result.Succeeded.Count != 1 {
// Should never happen, since we send nothing in the request
// that should cause a job to be skipped.
return nil, fmt.Errorf("job was skipped")
}
return result.Succeeded.Results[0].JobSummary(), nil
}
// DeleteJob deletes the job with the given id.
func (c *Client) DeleteJob(id string) error {
return c.delete([]string{"job", id})
}
func (c JobValueChoices) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
if len(c) > 0 {
return xml.Attr{name, strings.Join(c, ",")}, nil
} else {
return xml.Attr{}, nil
}
}
func (c *JobValueChoices) UnmarshalXMLAttr(attr xml.Attr) error {
values := strings.Split(attr.Value, ",")
*c = values
return nil
}
func (a JobCommandJobRefArguments) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
start.Attr = []xml.Attr{
xml.Attr{xml.Name{Local: "line"}, string(a)},
}
e.EncodeToken(start)
e.EncodeToken(xml.EndElement{start.Name})
return nil
}
func (a *JobCommandJobRefArguments) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type jobRefArgs struct {
Line string `xml:"line,attr"`
}
args := jobRefArgs{}
d.DecodeElement(&args, &start)
*a = JobCommandJobRefArguments(args.Line)
return nil
}
func (c JobPluginConfig) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
rc := map[string]string(c)
return marshalMapToXML(&rc, e, start, "entry", "key", "value")
}
func (c *JobPluginConfig) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
rc := (*map[string]string)(c)
return unmarshalMapFromXML(rc, d, start, "entry", "key", "value")
}
// JobSummary produces a JobSummary instance with values populated from the import result.
// The summary object won't have its Description populated, since import results do not
// include descriptions.
func (r *jobImportResult) JobSummary() *JobSummary {
return &JobSummary{
ID: r.ID,
Name: r.Name,
GroupName: r.GroupName,
ProjectName: r.ProjectName,
}
}