diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b80176c7..094caf2e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.4.8 - Added Job step option for `script_url` - Added ability to reference jobs in other projects through the use of `project_name` in `job` command type. +- Added support for Log Filters on individual Job Steps ## 0.4.7 - Added Password Resource diff --git a/rundeck/job.go b/rundeck/job.go index ebb97dc12..bf7e50436 100644 --- a/rundeck/job.go +++ b/rundeck/job.go @@ -281,6 +281,9 @@ type JobCommand struct { // Configuration for a step plugin to run as this command. StepPlugin *JobPlugin `xml:"step-plugin"` + // Configuration for log filter plugins to run for this command. + Plugins *JobPlugins `xml:"plugins"` + // Configuration for a node step plugin to run as this command. NodeStepPlugin *JobPlugin `xml:"node-step-plugin"` } @@ -314,6 +317,12 @@ type JobPlugin struct { Config JobPluginConfig `xml:"configuration"` } +// Plugin is a configuration for a filter plugin to run for a step +type JobPlugins struct { + XMLName xml.Name + LogFilterPlugins []JobLogFilter `xml:"LogFilter"` +} + // JobPluginConfig is a specialization of map[string]string for job plugin configuration. type JobPluginConfig map[string]string diff --git a/rundeck/resource_job.go b/rundeck/resource_job.go index 2970ffc62..65b5d3be8 100644 --- a/rundeck/resource_job.go +++ b/rundeck/resource_job.go @@ -380,12 +380,16 @@ func resourceRundeckJobCommand() *schema.Resource { Optional: true, Elem: resourceRundeckJobCommandJob(), }, - "step_plugin": { Type: schema.TypeList, Optional: true, Elem: resourceRundeckJobPluginResource(), }, + "plugins": { + Type: schema.TypeList, + Optional: true, + Elem: resourceRundeckJobPluginsResource(), + }, "node_step_plugin": { Type: schema.TypeList, Optional: true, @@ -450,13 +454,18 @@ func resourceRundeckJobCommandErrorHandler() *schema.Resource { Optional: true, Elem: resourceRundeckJobCommandJob(), }, - "step_plugin": { Type: schema.TypeList, Optional: true, Elem: resourceRundeckJobPluginResource(), }, + "plugins": { + Type: schema.TypeList, + Optional: true, + Elem: resourceRundeckJobPluginsResource(), + }, + "node_step_plugin": { Type: schema.TypeList, Optional: true, @@ -548,6 +557,18 @@ func resourceRundeckJobPluginResource() *schema.Resource { } } +func resourceRundeckJobPluginsResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "log_filter_plugin": { + Type: schema.TypeList, + Required: true, + Elem: resourceRundeckJobPluginResource(), + }, + }, + } +} + func resourceRundeckJobFilter() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -1205,6 +1226,9 @@ func commandFromResourceData(commandI interface{}) (*JobCommand, error) { if command.StepPlugin, err = singlePluginFromResourceData("step_plugin", commandMap); err != nil { return nil, err } + if command.Plugins, err = pluginsFromResourceData("plugins", commandMap); err != nil { + return nil, err + } if command.NodeStepPlugin, err = singlePluginFromResourceData("node_step_plugin", commandMap); err != nil { return nil, err } @@ -1243,6 +1267,32 @@ func jobCommandJobRefFromResourceData(key string, commandMap map[string]interfac return jobRef, nil } +func pluginsFromResourceData(key string, commandMap map[string]interface{}) (*JobPlugins, error) { + pluginsList := commandMap[key].([]interface{}) + if len(pluginsList) == 0 { + return nil, nil + } + if len(pluginsList) > 1 { + return nil, fmt.Errorf("rundeck command job reference may have no more than one plugins section") + } + pluginsI := pluginsList[0].(map[string]interface{}) + var plugins = new(JobPlugins) + for _, pluginI := range pluginsI["log_filter_plugin"].([]interface{}) { + pluginMap := pluginI.(map[string]interface{}) + configI := pluginMap["config"].(map[string]interface{}) + var config = JobLogFilterConfig{} + for key, value := range configI { + config[key] = value.(string) + } + plugin := &JobLogFilter{ + Type: pluginMap["type"].(string), + Config: &config, + } + plugins.LogFilterPlugins = append(plugins.LogFilterPlugins, *plugin) + } + return plugins, nil +} + func singlePluginFromResourceData(key string, commandMap map[string]interface{}) (*JobPlugin, error) { stepPluginsI := commandMap[key].([]interface{}) if len(stepPluginsI) > 1 { @@ -1311,7 +1361,6 @@ func commandToResourceData(command *JobCommand) (map[string]interface{}, error) } commandConfigI["job"] = append([]interface{}{}, jobRefConfigI) } - if command.StepPlugin != nil { commandConfigI["step_plugin"] = []interface{}{ map[string]interface{}{ @@ -1321,6 +1370,20 @@ func commandToResourceData(command *JobCommand) (map[string]interface{}, error) } } + if command.Plugins != nil { + logFilterPluginI := []map[string]interface{}{} + for _, plugin := range command.Plugins.LogFilterPlugins { + pluginI := map[string]interface{}{ + "type": plugin.Type, + "config": (map[string]string)(*plugin.Config), + } + logFilterPluginI = append(logFilterPluginI, pluginI) + } + commandConfigI["plugins"] = append([]interface{}{}, map[string]interface{}{ + "log_filter_plugin": logFilterPluginI, + }) + } + if command.NodeStepPlugin != nil { commandConfigI["node_step_plugin"] = []interface{}{ map[string]interface{}{ diff --git a/rundeck/resource_job_test.go b/rundeck/resource_job_test.go index 6e91e96bb..d4e008bf1 100644 --- a/rundeck/resource_job_test.go +++ b/rundeck/resource_job_test.go @@ -268,6 +268,44 @@ func TestAccJobOptions_secure_choice(t *testing.T) { }) } +func TestAccJob_plugins(t *testing.T) { + var job JobDetail + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccJobCheckDestroy(&job), + Steps: []resource.TestStep{ + { + Config: testAccJobConfig_plugins, + Check: resource.ComposeTestCheckFunc( + testAccJobCheckExists("rundeck_job.test", &job), + func(s *terraform.State) error { + jobCommand := job.CommandSequence.Commands[0] + if jobCommand.Plugins == nil { + return fmt.Errorf("JobCommands[0].plugins shouldn't be nil") + } + keyValuePlugin := jobCommand.Plugins.LogFilterPlugins[0] + if expected := "key-value-data"; keyValuePlugin.Type != expected { + return fmt.Errorf("wrong plugin type; expected %v, got %v", expected, keyValuePlugin.Type) + } + if expected := "\\s|\\$|\\{|\\}|\\\\"; (*keyValuePlugin.Config)["invalidKeyPattern"] != expected { + return fmt.Errorf("failed to set plugin config; expected %v for \"invalidKeyPattern\", got %v", expected, (*keyValuePlugin.Config)["invalidKeyPattern"]) + } + if expected := "true"; (*keyValuePlugin.Config)["logData"] != expected { + return fmt.Errorf("failed to set plugin config; expected %v for \"logData\", got %v", expected, (*keyValuePlugin.Config)["logData"]) + } + if expected := "^RUNDECK:DATA:\\s*([^\\s]+?)\\s*=\\s*(.+)$"; (*keyValuePlugin.Config)["regex"] != expected { + return fmt.Errorf("failed to set plugin config; expected %v for \"regex\", got %v", expected, (*keyValuePlugin.Config)["regex"]) + } + return nil + }, + ), + }, + }, + }) +} + const testAccJobConfig_basic = ` resource "rundeck_project" "test" { name = "terraform-acc-test-job" @@ -711,3 +749,57 @@ resource "rundeck_project" "test" { } } ` + +const testAccJobConfig_plugins = ` +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" + execution_enabled = true + node_filter_query = "example" + allow_concurrent_executions = true + nodes_selected_by_default = true + success_on_empty_node_filter = true + max_thread_count = 1 + rank_order = "ascending" + timeout = "42m" + schedule = "0 0 12 * * * *" + schedule_enabled = true + option { + name = "foo" + default_value = "bar" + } + command { + description = "Prints Hello World" + shell_command = "echo Hello World" + plugins { + log_filter_plugin { + config = { + invalidKeyPattern = "\\s|\\$|\\{|\\}|\\\\" + logData = "true" + regex = "^RUNDECK:DATA:\\s*([^\\s]+?)\\s*=\\s*(.+)$" + } + type = "key-value-data" + } + } + } + notification { + type = "on_success" + email { + recipients = ["foo@foo.bar"] + } + } +} +` diff --git a/website/docs/r/job.html.md b/website/docs/r/job.html.md index afa9a8e68..2676ced44 100644 --- a/website/docs/r/job.html.md +++ b/website/docs/r/job.html.md @@ -18,21 +18,58 @@ Each job belongs to a project. A project can be created with the `rundeck_projec ```hcl resource "rundeck_job" "bounceweb" { - name = "Bounce All Web Servers" - project_name = "${rundeck_project.terraform.name}" - node_filter_query = "tags: web" - description = "Restart the service daemons on all the web servers" + name = "Bounce All Web Servers" + project_name = "${rundeck_project.terraform.name}" + node_filter_query = "tags: web" + description = "Restart the service daemons on all the web servers" + + command { + shell_command = "sudo service anvils restart" + } + notification { + type = "on_success" + email { + recipients = ["example@foo.bar"] + } + } + } +``` + +## Example Usage (Key-Value Data Log filter to pass data between jobs) +```hcl + resource "rundeck_job" "update_review_environments" { + name = "Update review environments" + project_name = "${rundeck_project.terraform.name}" + node_filter_query = "tags: dev_server" + description = "Update the code in review environments checking out the given branch" + command { + description = null + inline_script = "#!/bin/sh\nenvironment_numbers=$(find /var/review_environments -mindepth 3 -maxdepth 4 -name '$1' | awk -F/ -vORS=, '{ print $3 }' | sed 's/.$//')\necho \"RUNDECK:DATA:environment_numbers=\"$environment_numbers\"" + script_file_args = "$${option.git_branch}" + plugins { + log_filter_plugin { + config = { + invalidKeyPattern = "\\s|\\$|\\{|\\}|\\\\" + logData = "true" + regex = "^RUNDECK:DATA:\\s*([^\\s]+?)\\s*=\\s*(.+)$" + } + type = "key-value-data" + } + } + } command { - shell_command = "sudo service anvils restart" + job { + args = "-environment_numbers $${data.environment_numbers}" + name = "git_pull_review_environments" + } } - notification { - type = "on_success" - email { - recipients = ["example@foo.bar"] - } + option { + name = "git_branch" + required = true } -} + } + ``` ## Argument Reference @@ -209,9 +246,10 @@ The following arguments are supported: * A `job` block, described below, causes another job within the same project to be executed as a command. - * A `step_plugin` block, described below, causes a step plugin to be executed as a command. +* A `plugins` block, described below, contains a list of plugins to add to the command. At the moment, only [Log Filters](https://docs.rundeck.com/docs/manual/log-filters/) are supported + * A `node_step_plugin` block, described below, causes a node step plugin to be executed once for each node. @@ -247,7 +285,11 @@ A command's `node_filters` block has the following structure: * `exclude_filter`: (Optional) The query string for nodes ***not to use***. -A command's `step_plugin` or `node_step_plugin` block both have the following structure, as does the job's +A command's `plugins` block has the following structure: + +* `log_filter_plugin`: A log filter plugin to add to the command. Can be repeated to add multiple log filters. See below for the structure. + +A command's `log_filter_plugin`, `step_plugin` or `node_step_plugin` block both have the following structure, as does the job's `global_log_filter` blocks: * `type`: (Required) The name of the plugin to execute.