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