github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/rundeck/resource_job.go (about) 1 package rundeck 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/terraform/helper/schema" 8 9 "github.com/apparentlymart/go-rundeck-api/rundeck" 10 ) 11 12 func resourceRundeckJob() *schema.Resource { 13 return &schema.Resource{ 14 Create: CreateJob, 15 Update: UpdateJob, 16 Delete: DeleteJob, 17 Exists: JobExists, 18 Read: ReadJob, 19 20 Schema: map[string]*schema.Schema{ 21 "id": &schema.Schema{ 22 Type: schema.TypeString, 23 Computed: true, 24 }, 25 26 "name": &schema.Schema{ 27 Type: schema.TypeString, 28 Required: true, 29 }, 30 31 "group_name": &schema.Schema{ 32 Type: schema.TypeString, 33 Optional: true, 34 ForceNew: true, 35 }, 36 37 "project_name": &schema.Schema{ 38 Type: schema.TypeString, 39 Required: true, 40 ForceNew: true, 41 }, 42 43 "description": &schema.Schema{ 44 Type: schema.TypeString, 45 Required: true, 46 }, 47 48 "log_level": &schema.Schema{ 49 Type: schema.TypeString, 50 Optional: true, 51 Default: "INFO", 52 }, 53 54 "allow_concurrent_executions": &schema.Schema{ 55 Type: schema.TypeBool, 56 Optional: true, 57 }, 58 59 "max_thread_count": &schema.Schema{ 60 Type: schema.TypeInt, 61 Optional: true, 62 Default: 1, 63 }, 64 65 "continue_on_error": &schema.Schema{ 66 Type: schema.TypeBool, 67 Optional: true, 68 }, 69 70 "rank_order": &schema.Schema{ 71 Type: schema.TypeString, 72 Optional: true, 73 Default: "ascending", 74 }, 75 76 "rank_attribute": &schema.Schema{ 77 Type: schema.TypeString, 78 Optional: true, 79 }, 80 81 "preserve_options_order": &schema.Schema{ 82 Type: schema.TypeBool, 83 Optional: true, 84 Computed: true, 85 }, 86 87 "command_ordering_strategy": &schema.Schema{ 88 Type: schema.TypeString, 89 Optional: true, 90 Default: "node-first", 91 }, 92 93 "node_filter_query": &schema.Schema{ 94 Type: schema.TypeString, 95 Optional: true, 96 }, 97 98 "node_filter_exclude_precedence": &schema.Schema{ 99 Type: schema.TypeBool, 100 Optional: true, 101 }, 102 103 "schedule": &schema.Schema{ 104 Type: schema.TypeString, 105 Optional: true, 106 }, 107 108 "option": &schema.Schema{ 109 // This is a list because order is important when preserve_options_order is 110 // set. When it's not set the order is unimportant but preserved by Rundeck/ 111 Type: schema.TypeList, 112 Optional: true, 113 Elem: &schema.Resource{ 114 Schema: map[string]*schema.Schema{ 115 "name": &schema.Schema{ 116 Type: schema.TypeString, 117 Required: true, 118 }, 119 120 "default_value": &schema.Schema{ 121 Type: schema.TypeString, 122 Optional: true, 123 }, 124 125 "value_choices": &schema.Schema{ 126 Type: schema.TypeList, 127 Optional: true, 128 Elem: &schema.Schema{ 129 Type: schema.TypeString, 130 }, 131 }, 132 133 "value_choices_url": &schema.Schema{ 134 Type: schema.TypeString, 135 Optional: true, 136 }, 137 138 "require_predefined_choice": &schema.Schema{ 139 Type: schema.TypeBool, 140 Optional: true, 141 }, 142 143 "validation_regex": &schema.Schema{ 144 Type: schema.TypeString, 145 Optional: true, 146 }, 147 148 "description": &schema.Schema{ 149 Type: schema.TypeString, 150 Optional: true, 151 }, 152 153 "required": &schema.Schema{ 154 Type: schema.TypeBool, 155 Optional: true, 156 }, 157 158 "allow_multiple_values": &schema.Schema{ 159 Type: schema.TypeBool, 160 Optional: true, 161 }, 162 163 "multi_value_delimiter": &schema.Schema{ 164 Type: schema.TypeString, 165 Optional: true, 166 }, 167 168 "obscure_input": &schema.Schema{ 169 Type: schema.TypeBool, 170 Optional: true, 171 }, 172 173 "exposed_to_scripts": &schema.Schema{ 174 Type: schema.TypeBool, 175 Optional: true, 176 }, 177 }, 178 }, 179 }, 180 181 "command": &schema.Schema{ 182 Type: schema.TypeList, 183 Required: true, 184 Elem: &schema.Resource{ 185 Schema: map[string]*schema.Schema{ 186 "shell_command": &schema.Schema{ 187 Type: schema.TypeString, 188 Optional: true, 189 }, 190 191 "inline_script": &schema.Schema{ 192 Type: schema.TypeString, 193 Optional: true, 194 }, 195 196 "script_file": &schema.Schema{ 197 Type: schema.TypeString, 198 Optional: true, 199 }, 200 201 "script_file_args": &schema.Schema{ 202 Type: schema.TypeString, 203 Optional: true, 204 }, 205 206 "job": &schema.Schema{ 207 Type: schema.TypeList, 208 Optional: true, 209 Elem: &schema.Resource{ 210 Schema: map[string]*schema.Schema{ 211 "name": &schema.Schema{ 212 Type: schema.TypeString, 213 Required: true, 214 }, 215 "group_name": &schema.Schema{ 216 Type: schema.TypeString, 217 Optional: true, 218 }, 219 "run_for_each_node": &schema.Schema{ 220 Type: schema.TypeBool, 221 Optional: true, 222 }, 223 "args": &schema.Schema{ 224 Type: schema.TypeString, 225 Optional: true, 226 }, 227 }, 228 }, 229 }, 230 231 "step_plugin": &schema.Schema{ 232 Type: schema.TypeList, 233 Optional: true, 234 Elem: resourceRundeckJobPluginResource(), 235 }, 236 237 "node_step_plugin": &schema.Schema{ 238 Type: schema.TypeList, 239 Optional: true, 240 Elem: resourceRundeckJobPluginResource(), 241 }, 242 }, 243 }, 244 }, 245 }, 246 } 247 } 248 249 func resourceRundeckJobPluginResource() *schema.Resource { 250 return &schema.Resource{ 251 Schema: map[string]*schema.Schema{ 252 "type": &schema.Schema{ 253 Type: schema.TypeString, 254 Required: true, 255 }, 256 "config": &schema.Schema{ 257 Type: schema.TypeMap, 258 Optional: true, 259 }, 260 }, 261 } 262 } 263 264 func CreateJob(d *schema.ResourceData, meta interface{}) error { 265 client := meta.(*rundeck.Client) 266 267 job, err := jobFromResourceData(d) 268 if err != nil { 269 return err 270 } 271 272 jobSummary, err := client.CreateJob(job) 273 if err != nil { 274 return err 275 } 276 277 d.SetId(jobSummary.ID) 278 d.Set("id", jobSummary.ID) 279 280 return ReadJob(d, meta) 281 } 282 283 func UpdateJob(d *schema.ResourceData, meta interface{}) error { 284 client := meta.(*rundeck.Client) 285 286 job, err := jobFromResourceData(d) 287 if err != nil { 288 return err 289 } 290 291 jobSummary, err := client.CreateOrUpdateJob(job) 292 if err != nil { 293 return err 294 } 295 296 d.SetId(jobSummary.ID) 297 d.Set("id", jobSummary.ID) 298 299 return ReadJob(d, meta) 300 } 301 302 func DeleteJob(d *schema.ResourceData, meta interface{}) error { 303 client := meta.(*rundeck.Client) 304 305 err := client.DeleteJob(d.Id()) 306 if err != nil { 307 return err 308 } 309 310 d.SetId("") 311 312 return nil 313 } 314 315 func JobExists(d *schema.ResourceData, meta interface{}) (bool, error) { 316 client := meta.(*rundeck.Client) 317 318 _, err := client.GetJob(d.Id()) 319 if err != nil { 320 if _, ok := err.(rundeck.NotFoundError); ok { 321 err = nil 322 } 323 return false, err 324 } 325 326 return true, nil 327 } 328 329 func ReadJob(d *schema.ResourceData, meta interface{}) error { 330 client := meta.(*rundeck.Client) 331 332 job, err := client.GetJob(d.Id()) 333 if err != nil { 334 return err 335 } 336 337 return jobToResourceData(job, d) 338 } 339 340 func jobFromResourceData(d *schema.ResourceData) (*rundeck.JobDetail, error) { 341 job := &rundeck.JobDetail{ 342 ID: d.Id(), 343 Name: d.Get("name").(string), 344 GroupName: d.Get("group_name").(string), 345 ProjectName: d.Get("project_name").(string), 346 Description: d.Get("description").(string), 347 LogLevel: d.Get("log_level").(string), 348 AllowConcurrentExecutions: d.Get("allow_concurrent_executions").(bool), 349 Dispatch: &rundeck.JobDispatch{ 350 MaxThreadCount: d.Get("max_thread_count").(int), 351 ContinueOnError: d.Get("continue_on_error").(bool), 352 RankAttribute: d.Get("rank_attribute").(string), 353 RankOrder: d.Get("rank_order").(string), 354 }, 355 } 356 357 sequence := &rundeck.JobCommandSequence{ 358 ContinueOnError: d.Get("continue_on_error").(bool), 359 OrderingStrategy: d.Get("command_ordering_strategy").(string), 360 Commands: []rundeck.JobCommand{}, 361 } 362 363 commandConfigs := d.Get("command").([]interface{}) 364 for _, commandI := range commandConfigs { 365 commandMap := commandI.(map[string]interface{}) 366 command := rundeck.JobCommand{ 367 ShellCommand: commandMap["shell_command"].(string), 368 Script: commandMap["inline_script"].(string), 369 ScriptFile: commandMap["script_file"].(string), 370 ScriptFileArgs: commandMap["script_file_args"].(string), 371 } 372 373 jobRefsI := commandMap["job"].([]interface{}) 374 if len(jobRefsI) > 1 { 375 return nil, fmt.Errorf("rundeck command may have no more than one job") 376 } 377 if len(jobRefsI) > 0 { 378 jobRefMap := jobRefsI[0].(map[string]interface{}) 379 command.Job = &rundeck.JobCommandJobRef{ 380 Name: jobRefMap["name"].(string), 381 GroupName: jobRefMap["group_name"].(string), 382 RunForEachNode: jobRefMap["run_for_each_node"].(bool), 383 Arguments: rundeck.JobCommandJobRefArguments(jobRefMap["args"].(string)), 384 } 385 } 386 387 stepPluginsI := commandMap["step_plugin"].([]interface{}) 388 if len(stepPluginsI) > 1 { 389 return nil, fmt.Errorf("rundeck command may have no more than one step plugin") 390 } 391 if len(stepPluginsI) > 0 { 392 stepPluginMap := stepPluginsI[0].(map[string]interface{}) 393 configI := stepPluginMap["config"].(map[string]interface{}) 394 config := map[string]string{} 395 for k, v := range configI { 396 config[k] = v.(string) 397 } 398 command.StepPlugin = &rundeck.JobPlugin{ 399 Type: stepPluginMap["type"].(string), 400 Config: config, 401 } 402 } 403 404 stepPluginsI = commandMap["node_step_plugin"].([]interface{}) 405 if len(stepPluginsI) > 1 { 406 return nil, fmt.Errorf("rundeck command may have no more than one node step plugin") 407 } 408 if len(stepPluginsI) > 0 { 409 stepPluginMap := stepPluginsI[0].(map[string]interface{}) 410 configI := stepPluginMap["config"].(map[string]interface{}) 411 config := map[string]string{} 412 for k, v := range configI { 413 config[k] = v.(string) 414 } 415 command.NodeStepPlugin = &rundeck.JobPlugin{ 416 Type: stepPluginMap["type"].(string), 417 Config: config, 418 } 419 } 420 421 sequence.Commands = append(sequence.Commands, command) 422 } 423 job.CommandSequence = sequence 424 425 optionConfigsI := d.Get("option").([]interface{}) 426 if len(optionConfigsI) > 0 { 427 optionsConfig := &rundeck.JobOptions{ 428 PreserveOrder: d.Get("preserve_options_order").(bool), 429 Options: []rundeck.JobOption{}, 430 } 431 for _, optionI := range optionConfigsI { 432 optionMap := optionI.(map[string]interface{}) 433 option := rundeck.JobOption{ 434 Name: optionMap["name"].(string), 435 DefaultValue: optionMap["default_value"].(string), 436 ValueChoices: rundeck.JobValueChoices([]string{}), 437 ValueChoicesURL: optionMap["value_choices_url"].(string), 438 RequirePredefinedChoice: optionMap["require_predefined_choice"].(bool), 439 ValidationRegex: optionMap["validation_regex"].(string), 440 Description: optionMap["description"].(string), 441 IsRequired: optionMap["required"].(bool), 442 AllowsMultipleValues: optionMap["allow_multiple_values"].(bool), 443 MultiValueDelimiter: optionMap["multi_value_delimiter"].(string), 444 ObscureInput: optionMap["obscure_input"].(bool), 445 ValueIsExposedToScripts: optionMap["exposed_to_scripts"].(bool), 446 } 447 448 for _, iv := range optionMap["value_choices"].([]interface{}) { 449 option.ValueChoices = append(option.ValueChoices, iv.(string)) 450 } 451 452 optionsConfig.Options = append(optionsConfig.Options, option) 453 } 454 job.OptionsConfig = optionsConfig 455 } 456 457 if d.Get("node_filter_query").(string) != "" { 458 job.NodeFilter = &rundeck.JobNodeFilter{ 459 ExcludePrecedence: d.Get("node_filter_exclude_precedence").(bool), 460 Query: d.Get("node_filter_query").(string), 461 } 462 } 463 464 if d.Get("schedule").(string) != "" { 465 schedule := strings.Split(d.Get("schedule").(string), " ") 466 if len(schedule) != 7 { 467 return nil, fmt.Errorf("Rundeck schedule must be formated like a cron expression, as defined here: http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-06.html") 468 } 469 job.Schedule = &rundeck.JobSchedule{ 470 Time: rundeck.JobScheduleTime{ 471 Seconds: schedule[0], 472 Minute: schedule[1], 473 Hour: schedule[2], 474 }, 475 Month: rundeck.JobScheduleMonth{ 476 Day: schedule[3], 477 Month: schedule[4], 478 }, 479 WeekDay: &rundeck.JobScheduleWeekDay{ 480 Day: schedule[5], 481 }, 482 Year: rundeck.JobScheduleYear{ 483 Year: schedule[6], 484 }, 485 } 486 } 487 488 return job, nil 489 } 490 491 func jobToResourceData(job *rundeck.JobDetail, d *schema.ResourceData) error { 492 493 d.SetId(job.ID) 494 d.Set("id", job.ID) 495 d.Set("name", job.Name) 496 d.Set("group_name", job.GroupName) 497 498 // The project name is not consistently returned in all rundeck versions, 499 // so we'll only update it if it's set. Jobs can't move between projects 500 // anyway, so this is harmless. 501 if job.ProjectName != "" { 502 d.Set("project_name", job.ProjectName) 503 } 504 505 d.Set("description", job.Description) 506 d.Set("log_level", job.LogLevel) 507 d.Set("allow_concurrent_executions", job.AllowConcurrentExecutions) 508 if job.Dispatch != nil { 509 d.Set("max_thread_count", job.Dispatch.MaxThreadCount) 510 d.Set("continue_on_error", job.Dispatch.ContinueOnError) 511 d.Set("rank_attribute", job.Dispatch.RankAttribute) 512 d.Set("rank_order", job.Dispatch.RankOrder) 513 } else { 514 d.Set("max_thread_count", nil) 515 d.Set("continue_on_error", nil) 516 d.Set("rank_attribute", nil) 517 d.Set("rank_order", nil) 518 } 519 520 d.Set("node_filter_query", nil) 521 d.Set("node_filter_exclude_precedence", nil) 522 if job.NodeFilter != nil { 523 d.Set("node_filter_query", job.NodeFilter.Query) 524 d.Set("node_filter_exclude_precedence", job.NodeFilter.ExcludePrecedence) 525 } 526 527 optionConfigsI := []interface{}{} 528 if job.OptionsConfig != nil { 529 d.Set("preserve_options_order", job.OptionsConfig.PreserveOrder) 530 for _, option := range job.OptionsConfig.Options { 531 optionConfigI := map[string]interface{}{ 532 "name": option.Name, 533 "default_value": option.DefaultValue, 534 "value_choices": option.ValueChoices, 535 "value_choices_url": option.ValueChoicesURL, 536 "require_predefined_choice": option.RequirePredefinedChoice, 537 "validation_regex": option.ValidationRegex, 538 "decription": option.Description, 539 "required": option.IsRequired, 540 "allow_multiple_values": option.AllowsMultipleValues, 541 "multi_value_delimiter": option.MultiValueDelimiter, 542 "obscure_input": option.ObscureInput, 543 "exposed_to_scripts": option.ValueIsExposedToScripts, 544 } 545 optionConfigsI = append(optionConfigsI, optionConfigI) 546 } 547 } 548 d.Set("option", optionConfigsI) 549 550 commandConfigsI := []interface{}{} 551 if job.CommandSequence != nil { 552 d.Set("command_ordering_strategy", job.CommandSequence.OrderingStrategy) 553 for _, command := range job.CommandSequence.Commands { 554 commandConfigI := map[string]interface{}{ 555 "shell_command": command.ShellCommand, 556 "inline_script": command.Script, 557 "script_file": command.ScriptFile, 558 "script_file_args": command.ScriptFileArgs, 559 } 560 561 if command.Job != nil { 562 commandConfigI["job"] = []interface{}{ 563 map[string]interface{}{ 564 "name": command.Job.Name, 565 "group_name": command.Job.GroupName, 566 "run_for_each_node": command.Job.RunForEachNode, 567 "args": command.Job.Arguments, 568 }, 569 } 570 } 571 572 if command.StepPlugin != nil { 573 commandConfigI["step_plugin"] = []interface{}{ 574 map[string]interface{}{ 575 "type": command.StepPlugin.Type, 576 "config": map[string]string(command.StepPlugin.Config), 577 }, 578 } 579 } 580 581 if command.NodeStepPlugin != nil { 582 commandConfigI["node_step_plugin"] = []interface{}{ 583 map[string]interface{}{ 584 "type": command.NodeStepPlugin.Type, 585 "config": map[string]string(command.NodeStepPlugin.Config), 586 }, 587 } 588 } 589 590 commandConfigsI = append(commandConfigsI, commandConfigI) 591 } 592 } 593 d.Set("command", commandConfigsI) 594 595 if job.Schedule != nil { 596 schedule := []string{} 597 schedule = append(schedule, job.Schedule.Time.Seconds) 598 schedule = append(schedule, job.Schedule.Time.Minute) 599 schedule = append(schedule, job.Schedule.Time.Hour) 600 schedule = append(schedule, job.Schedule.Month.Day) 601 schedule = append(schedule, job.Schedule.Month.Month) 602 if job.Schedule.WeekDay != nil { 603 schedule = append(schedule, job.Schedule.WeekDay.Day) 604 } else { 605 schedule = append(schedule, "*") 606 } 607 schedule = append(schedule, job.Schedule.Year.Year) 608 609 d.Set("schedule", strings.Join(schedule, " ")) 610 } 611 612 return nil 613 }