rundeck_job resource type.

This commit is contained in:
Martin Atkins 2015-06-25 18:00:23 -07:00
parent bb42821f74
commit 40aafe4455
3 changed files with 663 additions and 2 deletions

View File

@ -30,8 +30,8 @@ func Provider() terraform.ResourceProvider {
},
ResourcesMap: map[string]*schema.Resource{
"rundeck_project": resourceRundeckProject(),
//"rundeck_job": resourceRundeckJob(),
"rundeck_project": resourceRundeckProject(),
"rundeck_job": resourceRundeckJob(),
"rundeck_private_key": resourceRundeckPrivateKey(),
"rundeck_public_key": resourceRundeckPublicKey(),
},

View File

@ -0,0 +1,558 @@
package rundeck
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/apparentlymart/go-rundeck-api/rundeck"
)
func resourceRundeckJob() *schema.Resource {
return &schema.Resource{
Create: CreateJob,
Update: UpdateJob,
Delete: DeleteJob,
Exists: JobExists,
Read: ReadJob,
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"group_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"project_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"log_level": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "INFO",
},
"allow_concurrent_executions": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"max_thread_count": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 1,
},
"continue_on_error": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"rank_order": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ascending",
},
"rank_attribute": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"preserve_options_order": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"command_ordering_strategy": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "node-first",
},
"node_filter_query": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"node_filter_exclude_precedence": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"option": &schema.Schema{
// This is a list because order is important when preserve_options_order is
// set. When it's not set the order is unimportant but preserved by Rundeck/
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"default_value": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"value_choices": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"value_choices_url": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"require_predefined_choice": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"validation_regex": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"required": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"allow_multiple_values": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"multi_value_delimiter": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"obscure_input": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"exposed_to_scripts": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
},
},
},
"command": &schema.Schema{
Type: schema.TypeList,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"shell_command": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"inline_script": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"script_file": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"script_file_args": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"job": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"group_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"run_for_each_node": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"args": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
},
"step_plugin": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: resourceRundeckJobPluginResource(),
},
"node_step_plugin": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: resourceRundeckJobPluginResource(),
},
},
},
},
},
}
}
func resourceRundeckJobPluginResource() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"config": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
},
},
}
}
func CreateJob(d *schema.ResourceData, meta interface{}) error {
client := meta.(*rundeck.Client)
job, err := jobFromResourceData(d)
if err != nil {
return err
}
jobSummary, err := client.CreateJob(job)
if err != nil {
return err
}
d.SetId(jobSummary.ID)
d.Set("id", jobSummary.ID)
return ReadJob(d, meta)
}
func UpdateJob(d *schema.ResourceData, meta interface{}) error {
client := meta.(*rundeck.Client)
job, err := jobFromResourceData(d)
if err != nil {
return err
}
jobSummary, err := client.CreateOrUpdateJob(job)
if err != nil {
return err
}
d.SetId(jobSummary.ID)
d.Set("id", jobSummary.ID)
return ReadJob(d, meta)
}
func DeleteJob(d *schema.ResourceData, meta interface{}) error {
client := meta.(*rundeck.Client)
err := client.DeleteJob(d.Id())
if err != nil {
return err
}
d.SetId("")
return nil
}
func JobExists(d *schema.ResourceData, meta interface{}) (bool, error) {
client := meta.(*rundeck.Client)
_, err := client.GetJob(d.Id())
if err != nil {
if _, ok := err.(rundeck.NotFoundError); ok {
err = nil
}
return false, err
}
return true, nil
}
func ReadJob(d *schema.ResourceData, meta interface{}) error {
client := meta.(*rundeck.Client)
job, err := client.GetJob(d.Id())
if err != nil {
return err
}
return jobToResourceData(job, d)
}
func jobFromResourceData(d *schema.ResourceData) (*rundeck.JobDetail, error) {
job := &rundeck.JobDetail{
ID: d.Id(),
Name: d.Get("name").(string),
GroupName: d.Get("group_name").(string),
ProjectName: d.Get("project_name").(string),
Description: d.Get("description").(string),
LogLevel: d.Get("log_level").(string),
AllowConcurrentExecutions: d.Get("allow_concurrent_executions").(bool),
Dispatch: &rundeck.JobDispatch{
MaxThreadCount: d.Get("max_thread_count").(int),
ContinueOnError: d.Get("continue_on_error").(bool),
RankAttribute: d.Get("rank_attribute").(string),
RankOrder: d.Get("rank_order").(string),
},
}
sequence := &rundeck.JobCommandSequence{
ContinueOnError: d.Get("continue_on_error").(bool),
OrderingStrategy: d.Get("command_ordering_strategy").(string),
Commands: []rundeck.JobCommand{},
}
commandConfigs := d.Get("command").([]interface{})
for _, commandI := range commandConfigs {
commandMap := commandI.(map[string]interface{})
command := rundeck.JobCommand{
ShellCommand: commandMap["shell_command"].(string),
Script: commandMap["inline_script"].(string),
ScriptFile: commandMap["script_file"].(string),
ScriptFileArgs: commandMap["script_file_args"].(string),
}
jobRefsI := commandMap["job"].([]interface{})
if len(jobRefsI) > 1 {
return nil, fmt.Errorf("rundeck command may have no more than one job")
}
if len(jobRefsI) > 0 {
jobRefMap := jobRefsI[0].(map[string]interface{})
command.Job = &rundeck.JobCommandJobRef{
Name: jobRefMap["name"].(string),
GroupName: jobRefMap["group_name"].(string),
RunForEachNode: jobRefMap["run_for_each_node"].(bool),
Arguments: rundeck.JobCommandJobRefArguments(jobRefMap["args"].(string)),
}
}
stepPluginsI := commandMap["step_plugin"].([]interface{})
if len(stepPluginsI) > 1 {
return nil, fmt.Errorf("rundeck command may have no more than one step plugin")
}
if len(stepPluginsI) > 0 {
stepPluginMap := stepPluginsI[0].(map[string]interface{})
configI := stepPluginMap["config"].(map[string]interface{})
config := map[string]string{}
for k, v := range configI {
config[k] = v.(string)
}
command.StepPlugin = &rundeck.JobPlugin{
Type: stepPluginMap["type"].(string),
Config: config,
}
}
stepPluginsI = commandMap["node_step_plugin"].([]interface{})
if len(stepPluginsI) > 1 {
return nil, fmt.Errorf("rundeck command may have no more than one node step plugin")
}
if len(stepPluginsI) > 0 {
stepPluginMap := stepPluginsI[0].(map[string]interface{})
configI := stepPluginMap["config"].(map[string]interface{})
config := map[string]string{}
for k, v := range configI {
config[k] = v.(string)
}
command.NodeStepPlugin = &rundeck.JobPlugin{
Type: stepPluginMap["type"].(string),
Config: config,
}
}
sequence.Commands = append(sequence.Commands, command)
}
job.CommandSequence = sequence
optionConfigsI := d.Get("option").([]interface{})
if len(optionConfigsI) > 0 {
optionsConfig := &rundeck.JobOptions{
PreserveOrder: d.Get("preserve_options_order").(bool),
Options: []rundeck.JobOption{},
}
for _, optionI := range optionConfigsI {
optionMap := optionI.(map[string]interface{})
option := rundeck.JobOption{
Name: optionMap["name"].(string),
DefaultValue: optionMap["default_value"].(string),
ValueChoices: rundeck.JobValueChoices([]string{}),
ValueChoicesURL: optionMap["value_choices_url"].(string),
RequirePredefinedChoice: optionMap["require_predefined_choice"].(bool),
ValidationRegex: optionMap["validation_regex"].(string),
Description: optionMap["description"].(string),
IsRequired: optionMap["required"].(bool),
AllowsMultipleValues: optionMap["allow_multiple_values"].(bool),
MultiValueDelimiter: optionMap["multi_value_delimiter"].(string),
ObscureInput: optionMap["obscure_input"].(bool),
ValueIsExposedToScripts: optionMap["exposed_to_scripts"].(bool),
}
for _, iv := range optionMap["value_choices"].([]interface{}) {
option.ValueChoices = append(option.ValueChoices, iv.(string))
}
optionsConfig.Options = append(optionsConfig.Options, option)
}
job.OptionsConfig = optionsConfig
}
if d.Get("node_filter_query").(string) != "" {
job.NodeFilter = &rundeck.JobNodeFilter{
ExcludePrecedence: d.Get("node_filter_exclude_precedence").(bool),
Query: d.Get("node_filter_query").(string),
}
}
return job, nil
}
func jobToResourceData(job *rundeck.JobDetail, d *schema.ResourceData) error {
d.SetId(job.ID)
d.Set("id", job.ID)
d.Set("name", job.Name)
d.Set("group_name", job.GroupName)
d.Set("project_name", job.ProjectName)
d.Set("description", job.Description)
d.Set("log_level", job.LogLevel)
d.Set("allow_concurrent_executions", job.AllowConcurrentExecutions)
if job.Dispatch != nil {
d.Set("max_thread_count", job.Dispatch.MaxThreadCount)
d.Set("continue_on_error", job.Dispatch.ContinueOnError)
d.Set("rank_attribute", job.Dispatch.RankAttribute)
d.Set("rank_order", job.Dispatch.RankOrder)
} else {
d.Set("max_thread_count", nil)
d.Set("continue_on_error", nil)
d.Set("rank_attribute", nil)
d.Set("rank_order", nil)
}
d.Set("node_filter_query", nil)
d.Set("node_filter_exclude_precedence", nil)
if job.NodeFilter != nil {
d.Set("node_filter_query", job.NodeFilter.Query)
d.Set("node_filter_exclude_precedence", job.NodeFilter.ExcludePrecedence)
}
optionConfigsI := []interface{}{}
if job.OptionsConfig != nil {
d.Set("preserve_options_order", job.OptionsConfig.PreserveOrder)
for _, option := range job.OptionsConfig.Options {
optionConfigI := map[string]interface{}{
"name": option.Name,
"default_value": option.DefaultValue,
"value_choices": option.ValueChoices,
"value_choices_url": option.ValueChoicesURL,
"require_predefined_choice": option.RequirePredefinedChoice,
"validation_regex": option.ValidationRegex,
"decription": option.Description,
"required": option.IsRequired,
"allow_multiple_values": option.AllowsMultipleValues,
"multi_value_delimeter": option.MultiValueDelimiter,
"obscure_input": option.ObscureInput,
"exposed_to_scripts": option.ValueIsExposedToScripts,
}
optionConfigsI = append(optionConfigsI, optionConfigI)
}
}
d.Set("option", optionConfigsI)
commandConfigsI := []interface{}{}
if job.CommandSequence != nil {
d.Set("command_ordering_strategy", job.CommandSequence.OrderingStrategy)
for _, command := range job.CommandSequence.Commands {
commandConfigI := map[string]interface{}{
"shell_command": command.ShellCommand,
"inline_script": command.Script,
"script_file": command.ScriptFile,
"script_file_args": command.ScriptFileArgs,
}
if command.Job != nil {
commandConfigI["job"] = []interface{}{
map[string]interface{}{
"name": command.Job.Name,
"group_name": command.Job.GroupName,
"run_for_each_node": command.Job.RunForEachNode,
"args": command.Job.Arguments,
},
}
}
if command.StepPlugin != nil {
commandConfigI["step_plugin"] = []interface{}{
map[string]interface{}{
"type": command.StepPlugin.Type,
"config": map[string]string(command.StepPlugin.Config),
},
}
}
if command.NodeStepPlugin != nil {
commandConfigI["node_step_plugin"] = []interface{}{
map[string]interface{}{
"type": command.NodeStepPlugin.Type,
"config": map[string]string(command.NodeStepPlugin.Config),
},
}
}
commandConfigsI = append(commandConfigsI, commandConfigI)
}
}
d.Set("command", commandConfigsI)
return nil
}

View File

@ -0,0 +1,103 @@
package rundeck
import (
"fmt"
"testing"
"github.com/apparentlymart/go-rundeck-api/rundeck"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccJob_basic(t *testing.T) {
var job rundeck.JobDetail
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccJobCheckDestroy(&job),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccJobConfig_basic,
Check: resource.ComposeTestCheckFunc(
testAccJobCheckExists("rundeck_job.test", &job),
func(s *terraform.State) error {
if expected := "basic-job"; job.Name != expected {
return fmt.Errorf("wrong name; expected %v, got %v", expected, job.Name)
}
return nil
},
),
},
},
})
}
func testAccJobCheckDestroy(job *rundeck.JobDetail) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*rundeck.Client)
_, err := client.GetJob(job.ID)
if err == nil {
return fmt.Errorf("key still exists")
}
if _, ok := err.(*rundeck.NotFoundError); !ok {
return fmt.Errorf("got something other than NotFoundError (%v) when getting key", err)
}
return nil
}
}
func testAccJobCheckExists(rn string, job *rundeck.JobDetail) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[rn]
if !ok {
return fmt.Errorf("resource not found: %s", rn)
}
if rs.Primary.ID == "" {
return fmt.Errorf("job id not set")
}
client := testAccProvider.Meta().(*rundeck.Client)
gotJob, err := client.GetJob(rs.Primary.ID)
if err != nil {
return fmt.Errorf("error getting job details: %s", err)
}
*job = *gotJob
return nil
}
}
const testAccJobConfig_basic = `
resource "rundeck_project" "test" {
name = "terraform-acc-test-job"
description = "parent project for job acceptance tests"
resource_model_source {
type = "file"
config = {
format = "resourcexml"
file = "/tmp/terraform-acc-tests.xml"
}
}
}
resource "rundeck_job" "test" {
project_name = "${rundeck_project.test.name}"
name = "basic-job"
description = "A basic job"
node_filter_query = "example"
allow_concurrent_executions = 1
max_thread_count = 1
rank_order = "ascending"
option {
name = "foo"
default_value = "bar"
}
command {
shell_command = "echo Hello World"
}
}
`