github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/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  }