github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/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 "description": &schema.Schema{ 187 Type: schema.TypeString, 188 Optional: true, 189 }, 190 "shell_command": &schema.Schema{ 191 Type: schema.TypeString, 192 Optional: true, 193 }, 194 195 "inline_script": &schema.Schema{ 196 Type: schema.TypeString, 197 Optional: true, 198 }, 199 200 "script_file": &schema.Schema{ 201 Type: schema.TypeString, 202 Optional: true, 203 }, 204 205 "script_file_args": &schema.Schema{ 206 Type: schema.TypeString, 207 Optional: true, 208 }, 209 210 "job": &schema.Schema{ 211 Type: schema.TypeList, 212 Optional: true, 213 Elem: &schema.Resource{ 214 Schema: map[string]*schema.Schema{ 215 "name": &schema.Schema{ 216 Type: schema.TypeString, 217 Required: true, 218 }, 219 "group_name": &schema.Schema{ 220 Type: schema.TypeString, 221 Optional: true, 222 }, 223 "run_for_each_node": &schema.Schema{ 224 Type: schema.TypeBool, 225 Optional: true, 226 }, 227 "args": &schema.Schema{ 228 Type: schema.TypeString, 229 Optional: true, 230 }, 231 }, 232 }, 233 }, 234 235 "step_plugin": &schema.Schema{ 236 Type: schema.TypeList, 237 Optional: true, 238 Elem: resourceRundeckJobPluginResource(), 239 }, 240 241 "node_step_plugin": &schema.Schema{ 242 Type: schema.TypeList, 243 Optional: true, 244 Elem: resourceRundeckJobPluginResource(), 245 }, 246 }, 247 }, 248 }, 249 }, 250 } 251 } 252 253 func resourceRundeckJobPluginResource() *schema.Resource { 254 return &schema.Resource{ 255 Schema: map[string]*schema.Schema{ 256 "type": &schema.Schema{ 257 Type: schema.TypeString, 258 Required: true, 259 }, 260 "config": &schema.Schema{ 261 Type: schema.TypeMap, 262 Optional: true, 263 }, 264 }, 265 } 266 } 267 268 func CreateJob(d *schema.ResourceData, meta interface{}) error { 269 client := meta.(*rundeck.Client) 270 271 job, err := jobFromResourceData(d) 272 if err != nil { 273 return err 274 } 275 276 jobSummary, err := client.CreateJob(job) 277 if err != nil { 278 return err 279 } 280 281 d.SetId(jobSummary.ID) 282 d.Set("id", jobSummary.ID) 283 284 return ReadJob(d, meta) 285 } 286 287 func UpdateJob(d *schema.ResourceData, meta interface{}) error { 288 client := meta.(*rundeck.Client) 289 290 job, err := jobFromResourceData(d) 291 if err != nil { 292 return err 293 } 294 295 jobSummary, err := client.CreateOrUpdateJob(job) 296 if err != nil { 297 return err 298 } 299 300 d.SetId(jobSummary.ID) 301 d.Set("id", jobSummary.ID) 302 303 return ReadJob(d, meta) 304 } 305 306 func DeleteJob(d *schema.ResourceData, meta interface{}) error { 307 client := meta.(*rundeck.Client) 308 309 err := client.DeleteJob(d.Id()) 310 if err != nil { 311 return err 312 } 313 314 d.SetId("") 315 316 return nil 317 } 318 319 func JobExists(d *schema.ResourceData, meta interface{}) (bool, error) { 320 client := meta.(*rundeck.Client) 321 322 _, err := client.GetJob(d.Id()) 323 if err != nil { 324 if _, ok := err.(rundeck.NotFoundError); ok { 325 err = nil 326 } 327 return false, err 328 } 329 330 return true, nil 331 } 332 333 func ReadJob(d *schema.ResourceData, meta interface{}) error { 334 client := meta.(*rundeck.Client) 335 336 job, err := client.GetJob(d.Id()) 337 if err != nil { 338 return err 339 } 340 341 return jobToResourceData(job, d) 342 } 343 344 func jobFromResourceData(d *schema.ResourceData) (*rundeck.JobDetail, error) { 345 job := &rundeck.JobDetail{ 346 ID: d.Id(), 347 Name: d.Get("name").(string), 348 GroupName: d.Get("group_name").(string), 349 ProjectName: d.Get("project_name").(string), 350 Description: d.Get("description").(string), 351 LogLevel: d.Get("log_level").(string), 352 AllowConcurrentExecutions: d.Get("allow_concurrent_executions").(bool), 353 Dispatch: &rundeck.JobDispatch{ 354 MaxThreadCount: d.Get("max_thread_count").(int), 355 ContinueOnError: d.Get("continue_on_error").(bool), 356 RankAttribute: d.Get("rank_attribute").(string), 357 RankOrder: d.Get("rank_order").(string), 358 }, 359 } 360 361 sequence := &rundeck.JobCommandSequence{ 362 ContinueOnError: d.Get("continue_on_error").(bool), 363 OrderingStrategy: d.Get("command_ordering_strategy").(string), 364 Commands: []rundeck.JobCommand{}, 365 } 366 367 commandConfigs := d.Get("command").([]interface{}) 368 for _, commandI := range commandConfigs { 369 commandMap := commandI.(map[string]interface{}) 370 command := rundeck.JobCommand{ 371 Description: commandMap["description"].(string), 372 ShellCommand: commandMap["shell_command"].(string), 373 Script: commandMap["inline_script"].(string), 374 ScriptFile: commandMap["script_file"].(string), 375 ScriptFileArgs: commandMap["script_file_args"].(string), 376 } 377 378 jobRefsI := commandMap["job"].([]interface{}) 379 if len(jobRefsI) > 1 { 380 return nil, fmt.Errorf("rundeck command may have no more than one job") 381 } 382 if len(jobRefsI) > 0 { 383 jobRefMap := jobRefsI[0].(map[string]interface{}) 384 command.Job = &rundeck.JobCommandJobRef{ 385 Name: jobRefMap["name"].(string), 386 GroupName: jobRefMap["group_name"].(string), 387 RunForEachNode: jobRefMap["run_for_each_node"].(bool), 388 Arguments: rundeck.JobCommandJobRefArguments(jobRefMap["args"].(string)), 389 } 390 } 391 392 stepPluginsI := commandMap["step_plugin"].([]interface{}) 393 if len(stepPluginsI) > 1 { 394 return nil, fmt.Errorf("rundeck command may have no more than one step plugin") 395 } 396 if len(stepPluginsI) > 0 { 397 stepPluginMap := stepPluginsI[0].(map[string]interface{}) 398 configI := stepPluginMap["config"].(map[string]interface{}) 399 config := map[string]string{} 400 for k, v := range configI { 401 config[k] = v.(string) 402 } 403 command.StepPlugin = &rundeck.JobPlugin{ 404 Type: stepPluginMap["type"].(string), 405 Config: config, 406 } 407 } 408 409 stepPluginsI = commandMap["node_step_plugin"].([]interface{}) 410 if len(stepPluginsI) > 1 { 411 return nil, fmt.Errorf("rundeck command may have no more than one node step plugin") 412 } 413 if len(stepPluginsI) > 0 { 414 stepPluginMap := stepPluginsI[0].(map[string]interface{}) 415 configI := stepPluginMap["config"].(map[string]interface{}) 416 config := map[string]string{} 417 for k, v := range configI { 418 config[k] = v.(string) 419 } 420 command.NodeStepPlugin = &rundeck.JobPlugin{ 421 Type: stepPluginMap["type"].(string), 422 Config: config, 423 } 424 } 425 426 sequence.Commands = append(sequence.Commands, command) 427 } 428 job.CommandSequence = sequence 429 430 optionConfigsI := d.Get("option").([]interface{}) 431 if len(optionConfigsI) > 0 { 432 optionsConfig := &rundeck.JobOptions{ 433 PreserveOrder: d.Get("preserve_options_order").(bool), 434 Options: []rundeck.JobOption{}, 435 } 436 for _, optionI := range optionConfigsI { 437 optionMap := optionI.(map[string]interface{}) 438 option := rundeck.JobOption{ 439 Name: optionMap["name"].(string), 440 DefaultValue: optionMap["default_value"].(string), 441 ValueChoices: rundeck.JobValueChoices([]string{}), 442 ValueChoicesURL: optionMap["value_choices_url"].(string), 443 RequirePredefinedChoice: optionMap["require_predefined_choice"].(bool), 444 ValidationRegex: optionMap["validation_regex"].(string), 445 Description: optionMap["description"].(string), 446 IsRequired: optionMap["required"].(bool), 447 AllowsMultipleValues: optionMap["allow_multiple_values"].(bool), 448 MultiValueDelimiter: optionMap["multi_value_delimiter"].(string), 449 ObscureInput: optionMap["obscure_input"].(bool), 450 ValueIsExposedToScripts: optionMap["exposed_to_scripts"].(bool), 451 } 452 453 for _, iv := range optionMap["value_choices"].([]interface{}) { 454 option.ValueChoices = append(option.ValueChoices, iv.(string)) 455 } 456 457 optionsConfig.Options = append(optionsConfig.Options, option) 458 } 459 job.OptionsConfig = optionsConfig 460 } 461 462 if d.Get("node_filter_query").(string) != "" { 463 job.NodeFilter = &rundeck.JobNodeFilter{ 464 ExcludePrecedence: d.Get("node_filter_exclude_precedence").(bool), 465 Query: d.Get("node_filter_query").(string), 466 } 467 } 468 469 if d.Get("schedule").(string) != "" { 470 schedule := strings.Split(d.Get("schedule").(string), " ") 471 if len(schedule) != 7 { 472 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") 473 } 474 job.Schedule = &rundeck.JobSchedule{ 475 Time: rundeck.JobScheduleTime{ 476 Seconds: schedule[0], 477 Minute: schedule[1], 478 Hour: schedule[2], 479 }, 480 Month: rundeck.JobScheduleMonth{ 481 Day: schedule[3], 482 Month: schedule[4], 483 }, 484 WeekDay: &rundeck.JobScheduleWeekDay{ 485 Day: schedule[5], 486 }, 487 Year: rundeck.JobScheduleYear{ 488 Year: schedule[6], 489 }, 490 } 491 } 492 493 return job, nil 494 } 495 496 func jobToResourceData(job *rundeck.JobDetail, d *schema.ResourceData) error { 497 498 d.SetId(job.ID) 499 d.Set("id", job.ID) 500 d.Set("name", job.Name) 501 d.Set("group_name", job.GroupName) 502 503 // The project name is not consistently returned in all rundeck versions, 504 // so we'll only update it if it's set. Jobs can't move between projects 505 // anyway, so this is harmless. 506 if job.ProjectName != "" { 507 d.Set("project_name", job.ProjectName) 508 } 509 510 d.Set("description", job.Description) 511 d.Set("log_level", job.LogLevel) 512 d.Set("allow_concurrent_executions", job.AllowConcurrentExecutions) 513 if job.Dispatch != nil { 514 d.Set("max_thread_count", job.Dispatch.MaxThreadCount) 515 d.Set("continue_on_error", job.Dispatch.ContinueOnError) 516 d.Set("rank_attribute", job.Dispatch.RankAttribute) 517 d.Set("rank_order", job.Dispatch.RankOrder) 518 } else { 519 d.Set("max_thread_count", nil) 520 d.Set("continue_on_error", nil) 521 d.Set("rank_attribute", nil) 522 d.Set("rank_order", nil) 523 } 524 525 d.Set("node_filter_query", nil) 526 d.Set("node_filter_exclude_precedence", nil) 527 if job.NodeFilter != nil { 528 d.Set("node_filter_query", job.NodeFilter.Query) 529 d.Set("node_filter_exclude_precedence", job.NodeFilter.ExcludePrecedence) 530 } 531 532 optionConfigsI := []interface{}{} 533 if job.OptionsConfig != nil { 534 d.Set("preserve_options_order", job.OptionsConfig.PreserveOrder) 535 for _, option := range job.OptionsConfig.Options { 536 optionConfigI := map[string]interface{}{ 537 "name": option.Name, 538 "default_value": option.DefaultValue, 539 "value_choices": option.ValueChoices, 540 "value_choices_url": option.ValueChoicesURL, 541 "require_predefined_choice": option.RequirePredefinedChoice, 542 "validation_regex": option.ValidationRegex, 543 "decription": option.Description, 544 "required": option.IsRequired, 545 "allow_multiple_values": option.AllowsMultipleValues, 546 "multi_value_delimiter": option.MultiValueDelimiter, 547 "obscure_input": option.ObscureInput, 548 "exposed_to_scripts": option.ValueIsExposedToScripts, 549 } 550 optionConfigsI = append(optionConfigsI, optionConfigI) 551 } 552 } 553 d.Set("option", optionConfigsI) 554 555 commandConfigsI := []interface{}{} 556 if job.CommandSequence != nil { 557 d.Set("command_ordering_strategy", job.CommandSequence.OrderingStrategy) 558 for _, command := range job.CommandSequence.Commands { 559 commandConfigI := map[string]interface{}{ 560 "description": command.Description, 561 "shell_command": command.ShellCommand, 562 "inline_script": command.Script, 563 "script_file": command.ScriptFile, 564 "script_file_args": command.ScriptFileArgs, 565 } 566 567 if command.Job != nil { 568 commandConfigI["job"] = []interface{}{ 569 map[string]interface{}{ 570 "name": command.Job.Name, 571 "group_name": command.Job.GroupName, 572 "run_for_each_node": command.Job.RunForEachNode, 573 "args": command.Job.Arguments, 574 }, 575 } 576 } 577 578 if command.StepPlugin != nil { 579 commandConfigI["step_plugin"] = []interface{}{ 580 map[string]interface{}{ 581 "type": command.StepPlugin.Type, 582 "config": map[string]string(command.StepPlugin.Config), 583 }, 584 } 585 } 586 587 if command.NodeStepPlugin != nil { 588 commandConfigI["node_step_plugin"] = []interface{}{ 589 map[string]interface{}{ 590 "type": command.NodeStepPlugin.Type, 591 "config": map[string]string(command.NodeStepPlugin.Config), 592 }, 593 } 594 } 595 596 commandConfigsI = append(commandConfigsI, commandConfigI) 597 } 598 } 599 d.Set("command", commandConfigsI) 600 601 if job.Schedule != nil { 602 schedule := []string{} 603 schedule = append(schedule, job.Schedule.Time.Seconds) 604 schedule = append(schedule, job.Schedule.Time.Minute) 605 schedule = append(schedule, job.Schedule.Time.Hour) 606 schedule = append(schedule, job.Schedule.Month.Day) 607 schedule = append(schedule, job.Schedule.Month.Month) 608 if job.Schedule.WeekDay != nil { 609 schedule = append(schedule, job.Schedule.WeekDay.Day) 610 } else { 611 schedule = append(schedule, "*") 612 } 613 schedule = append(schedule, job.Schedule.Year.Year) 614 615 d.Set("schedule", strings.Join(schedule, " ")) 616 } 617 618 return nil 619 }