Skip to content

Commit

Permalink
Merge pull request #135 from rundeck/log-filters
Browse files Browse the repository at this point in the history
Log filters implemented by #131 

Tested and worked great.
  • Loading branch information
fdevans authored Sep 20, 2024
2 parents 6c34ae9 + ddc4161 commit ce4912e
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
9 changes: 9 additions & 0 deletions rundeck/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down Expand Up @@ -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

Expand Down
69 changes: 66 additions & 3 deletions rundeck/resource_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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{}{
Expand All @@ -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{}{
Expand Down
92 changes: 92 additions & 0 deletions rundeck/resource_job_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 = ["[email protected]"]
}
}
}
`
68 changes: 55 additions & 13 deletions website/docs/r/job.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ["[email protected]"]
}
}
}
```

## 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 = ["[email protected]"]
}
option {
name = "git_branch"
required = true
}
}
}
```

## Argument Reference
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit ce4912e

Please sign in to comment.