github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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  }