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  }