hashicorp/terraform

Rundeck Provider: rundeck_job doesn't have idempotency

Closed this issue · 12 comments

Terraform Version

Terraform v0.7.3

Affected Resource(s)

  • rundeck_job

Terraform Configuration Files

resource "rundeck_job" "example" {
  name = "example"
  project_name = "test"
  description = "Testing idempotency"
  group_name = "Terraform"
  allow_concurrent_executions = "false"

  option {
    name = "instance_count"
    default_value = "2"
    required = "true"
    value_choices = ["1,2,3,4,5,6,7,8,9"]
    require_predefined_choice = "true"
  }

  command {
    shell_command = "echo hello"
  }
}

Debug Output

+ rundeck_job.example
    allow_concurrent_executions:        "false"
    command.#:                          "1"
    command.0.shell_command:            "echo hello"
    command_ordering_strategy:          "node-first"
    description:                        "Testing idempotency"
    group_name:                         "Terraform"
    log_level:                          "INFO"
    max_thread_count:                   "1"
    name:                               "example"
    option.#:                           "1"
    option.0.default_value:             "2"
    option.0.name:                      "instance_count"
    option.0.require_predefined_choice: "true"
    option.0.required:                  "true"
    option.0.value_choices.#:           "1"
    option.0.value_choices.0:           "1,2,3,4,5,6,7,8,9"
    project_name:                       "test"
    rank_order:                         "ascending"

data.aws_availability_zones.available: Refreshing state...
rundeck_job.example: Creating...
  allow_concurrent_executions:        "" => "false"
  command.#:                          "" => "1"
  command.0.shell_command:            "" => "echo hello"
  command_ordering_strategy:          "" => "node-first"
  description:                        "" => "Testing idempotency"
  group_name:                         "" => "Terraform"
  log_level:                          "" => "INFO"
  max_thread_count:                   "" => "1"
  name:                               "" => "example"
  option.#:                           "" => "1"
  option.0.default_value:             "" => "2"
  option.0.name:                      "" => "instance_count"
  option.0.require_predefined_choice: "" => "true"
  option.0.required:                  "" => "true"
  option.0.value_choices.#:           "" => "1"
  option.0.value_choices.0:           "" => "1,2,3,4,5,6,7,8,9"
  project_name:                       "" => "test"
  rank_order:                         "" => "ascending"
rundeck_job.example: Creation complete

~ rundeck_job.example
    allow_concurrent_executions: "true" => "false"
    max_thread_count:            "0" => "1"
    preserve_options_order:      "true" => "false"
    rank_order:                  "" => "ascending"

Expected Behavior

I should be able to apply the rundeck_job multiple times without any changes.

Actual Behavior

After applying the changes it'll try and change allow_concurrent_executions (which was previously specified to be false), max_thread_count, preserve_options_order, and rank_order.

After applying several times the same arguments will still appear.

rundeck_job.example: Refreshing state... (ID: 7cafe700-47d3-4091-9249-65e21d3ee394)
data.aws_availability_zones.available: Refreshing state...
rundeck_job.example: Modifying...
  allow_concurrent_executions: "true" => "false"
  max_thread_count:            "0" => "1"
  preserve_options_order:      "true" => "false"
  rank_order:                  "" => "ascending"
rundeck_job.example: Modifications complete

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

~ rundeck_job.example
    allow_concurrent_executions: "true" => "false"
    max_thread_count:            "0" => "1"
    preserve_options_order:      "true" => "false"
    rank_order:                  "" => "ascending"


Plan: 0 to add, 1 to change, 0 to destroy.

Steps to Reproduce

Please list the steps required to reproduce the issue, for example:

  1. terraform apply
  2. terraform apply

Important Factoids

Rundeck Pro 1.3.9 (2.6.9-1)

@BobVanB has a whole set of Rundeck provider improvements in #8314, and I think this is one of the things he addressed, at least in part.

terraform(latest) and rundeck (2.6.7-1)
debugged: allow_concurrent_executions part

allow_concurrent_executions = "false" should be allow_concurrent_executions = false
The type of allow_concurrent_executions is a boolean:

"allow_concurrent_executions": &schema.Schema{
    Type:     schema.TypeBool,
    Optional: true,
},

Your code will produce the following xml(exported by rundeck):

<joblist>
  <job>
    <context>
      <options preserveOrder='true'>
        <option enforcedvalues='true' name='instance_count' required='true' value='2' values='1,2,3,4,5,6,7,8,9' />
      </options>
    </context>
    <description>Testing idempotency</description>
    <executionEnabled>true</executionEnabled>
    <group>Terraform</group>
    <id>bf31e454-c4c6-433d-99cb-ce2261b817e1</id>
    <loglevel>INFO</loglevel>
    <multipleExecutions>true</multipleExecutions>
    <name>example</name>
    <scheduleEnabled>true</scheduleEnabled>
    <sequence keepgoing='false' strategy='node-first'>
      <command>
        <exec>echo hello</exec>
      </command>
    </sequence>
    <uuid>bf31e454-c4c6-433d-99cb-ce2261b817e1</uuid>
  </job>
</joblist>

(export the job xml and you can see it for your self)
Rundeck has the following behavior in the ui:

  1. new job: multipleExecutions is not present, defaults to: Multiple Executions? No
  2. enable multipleExecutions: multipleExecutions is present and it is set to true.
  3. disable multipleExecutions: multipleExecutions is not present
  4. import job with 'Update the existing Job' and <multipleExecutions>false</multipleExecutions>, rundeck noticed the element is present and sets multipleExecutions back to true
  5. step 4, only with create new job, same results as 4.

This means that in the go-rundeck-api the xml should be set to:

type JobDetail struct {
  AllowConcurrentExecutions bool                `xml:"multipleExecutions,omitempty"`
}

The above is the fix to go for the go-rundeck-api and is already merged.
https://github.com/apparentlymart/go-rundeck-api/blob/master/rundeck/job.go#L35

The solution for terraform is that it should use it only if its true, so something like:

func jobFromResourceData(d *schema.ResourceData) (*rundeck.JobDetail, error) {
    job := &rundeck.JobDetail{}
    if d.Get("allow_concurrent_executions").(bool) {
        job.AllowConcurrentExecutions = true,
    }
}

@jamielennox1 The solution to your problem with allow_concurrent_executions is remove it from your terraform and only set it if its true.

allow_concurrent_executions = "false" <= this is default

terraform(latest) and rundeck (2.6.7-1)
debugged: max_thread_count part

This one is easy.
This is how the xml from rundeck looks like:

<joblist>
  <job>
    <dispatch>
      <excludePrecedence>true</excludePrecedence>
      <keepgoing>false</keepgoing>
      <rankOrder>ascending</rankOrder>
      <threadcount>1</threadcount>
    </dispatch>
  </job>
</joblist>

As you can see the threadcount is in the element dispatch, so if you pass this option with terraform.
Rundeck will not apply this setting, and terraform will always trying to change this.

The terraform schema is as followed:

            "max_thread_count": &schema.Schema{
                Type:     schema.TypeInt,
                Optional: true,
                Default:  1,
            },

The default means that it will always set it to one, if this value is not supplied by the user.

The solution is to remove the 'Default', because rundeck will use its internal default of 1.

But this is something that is bugging me, the max_thread_count should be in the element dispatch.
There it can and must have a default, that way terraform will not set it unless the element dispath is supplied. So the max_thread_level is in terraform on the wrong level.
Like this:

            // Element: Job>Dispatch
            "dispatch": &schema.Schema{
                Type:     schema.TypeList,
                Optional: true,
                MaxItems: 1,
                Elem: &schema.Resource{
                    Schema: map[string]*schema.Schema{
                        "max_thread_count": &schema.Schema{
                            Type:     schema.TypeInt,
                            Optional: true,
                            Default: 1,
                        },
                    },
                },
            },
resource "rundeck_job" "example" {
  name = "example"
  project_name = "test"
  description = "Testing idempotency"
  group_name = "Terraform"

  dispatch {
    max_thread_count = 1
  }

  command {
    shell_command = "echo hello"
  }
}
  • The default value will give a new problem when node_filter_query is set.
~ module.test.rundeck_job.example
    max_thread_count:          "1" => "0"
    nodes_selected_by_default: "true" => "false"
    rank_order:                "ascending" => ""

terraform(latest) and rundeck (2.6.7-1)
debugged: preserve_options_order part

This one is also fun.
The exported document always is always true and always present on 'job>context>options'
The ui of rundeck has no switch to turn this on or off, there default is, always order on alphabet.

  • This means that when you apply with TF, then save with rundeck the order is gone.
    The terraform default for preserve_options_order is false (if not exists)

The above means rundeck translate preserveOrder from false to true.
The above will always mismatch between TF and Rundeck export. therefore the change.

The correct solution would be that terraform only checks the value when it has preserve_options_order declared by the user.

The terraform code is correct, it is optional, only problem is that it only should be checked when there is at least one option present.

            "preserve_options_order": &schema.Schema{
                Type:     schema.TypeBool,
                Optional: true,
            },

Add that with the normal problems of the boolean type.

When applying the job definition to rundeck the value can be true, false and omitted. The value is upheld.

I have no solution for this, the one that is the least annoying for me is if i set preserver_options_order default on true, because this is a setting i always use is always true.

            "preserve_options_order": &schema.Schema{
                Type:     schema.TypeBool,
                Optional: true,
                Default:  true,
            },

Scenario's with Default true;

  1. preserve_options_order present and true with options
    has no changes
  2. preserve_options_order present and false with options
    always shows: preserve_options_order: "true" => "false"
  3. preserve_options_order not present with option
    has no changes
  4. preserve_options_order present and true without options
    has no changes
  5. preserve_options_order present and false without options
    has no changes
  6. preserve_options_order not present without options
    has no changes

As you can see above, the only one that is enoying is test 2, well to remove that one, i will order my options by hand and just remove preserve_options_order = false

terraform(latest) and rundeck (2.6.7-1)
debugged: preserve_options_order part

The same as max_thread_count.

Because this option is on the top level of the terraform schema the code should be:

            "rank_order": &schema.Schema{
                Type:     schema.TypeString,
                Optional: true,
                Default:  "ascending",
            },

This one also should be inside the dispatch element next to max_thread_count:

            // Element: Job>Dispatch
            "dispatch": &schema.Schema{
                Type:     schema.TypeList,
                Optional: true,
                MaxItems: 1,
                Elem: &schema.Resource{
                    Schema: map[string]*schema.Schema{
                        "max_thread_count": &schema.Schema{
                            Type:     schema.TypeInt,
                            Optional: true,
                            Default: 1,
                        },
                        "rank_order": &schema.Schema{
                            Type:     schema.TypeString,
                            Optional: true,
                        },
                    },
                },
            },

Well lets make it complete shall we:

            // Element: Job>Dispatch
            "dispatch": &schema.Schema{
                Type:     schema.TypeList,
                Optional: true,
                MaxItems: 1,
                Elem: &schema.Resource{
                    Schema: map[string]*schema.Schema{
                        "max_thread_count": &schema.Schema{
                            Type:     schema.TypeInt,
                            Optional: true,
                            Default: 1,
                        },
                        "rank_order": &schema.Schema{
                            Type:     schema.TypeString,
                            Optional: true,
                            Default: "ascending",
                            ValidateFunc: validateValueFunc([]string{"ascending", "descending"}),
                        },
                        // This will overwrite the continue_on_error on the jobdetails.
                        "continue_on_error": &schema.Schema{
                            Type:     schema.TypeBool,
                            Optional: true,
                            Default: false
                        },
                    },
                },
            },
  • validateValueFunc is copied from github utils provider in to rundeck/util.go

As long as it is not in its own dispatch element, the default value and that element will give trouble.

I'm running into this sort of issue currently.

using rundeck 2.6.11, and for every build job i'm creating I get modifications on max_thread_count and rank_order:

rundeck_job.set_build.19: Modifying... (ID: 9b5c8c7b-4378-450b-9012-9c3d41546537)
  max_thread_count: "0" => "1"
  rank_order:       "" => "ascending"
rundeck_job.set_build.17: Modifications complete (ID: 5b655a32-8b12-4a26-8fe5-a0f2541b26b2)
rundeck_job.set_build.24: Modifying... (ID: cb96f8da-b364-46c2-9474-0b95bc064e2e)
  max_thread_count: "0" => "1"
  rank_order:       "" => "ascending"
rundeck_job.set_build.10: Modifications complete (ID: 98bea7fa-e32f-4279-b2b4-fec2198f8d63)
rundeck_job.set_build.16: Modifying... (ID: aa7fee2c-d3a5-4217-b3c9-7d0563574f76)
  max_thread_count: "0" => "1"
  rank_order:       "" => "ascending"
rundeck_job.set_build.9: Modifications complete (ID: 9e676f79-e427-4ee3-ba9b-e1253d850c6f)
rundeck_job.set_build.5: Modifying... (ID: 91255607-9a05-4e8e-a927-a7d2b9f7fe07)
  max_thread_count: "0" => "1"
  rank_order:       "" => "ascending"
rundeck_job.set_build.14: Modifications complete (ID: a03b3db9-dcf5-40bf-8ebe-9c0672459827)
rundeck_job.set_build.0: Modifying... (ID: 641c2e35-1632-48ad-a193-675bb6962f1f)
  max_thread_count: "0" => "1"
  rank_order:       "" => "ascending"
rundeck_job.set_build.26: Modifications complete (ID: 1bb4c750-5a84-46dc-8bf1-0ef4e660ce9f)

This happens for a few hundred jobs created by this count list; so to keep me sane (since we look for changes), I've had to add:

  lifecycle {
    ignore_changes = ["max_thread_count","rank_order"]
  }

@mengesb yup that is anoying.
We currently are migratie to jobs in git with the scm plugin and filling the projects config with a custom script through the api.
Terraform is nice, but the development for rundeck is not active enough.

Proof, 2 months ago:
#13156

Sorry for the delayed work on Rundeck, all. This is all on me, since I shifted roles from my previous employer, where maintaining the Rundeck provider was part of my job, to working at Hashicorp, where unfortunately my attention shifted to Terraform core. 😞

Rundeck is considered to be a "community provider" by Hashicorp, which is to say that we depend on community members for its ongoing development. However, I dropped the ball and stopped paying enough attention to the great contributions from @BobVanB and for that I apologize. Unfortunately Rundeck itself seems to move pretty quickly, so it can be hard to keep up with their new API versions. However, if you have the time and motivation to get the PR passing tests against latest master then I will merge it.

@apparentlymart no problem at all. We are still running the 2.7.3 branche. It does what it suppose to do. Indeed new versions can take up time
But with alot of projects and jobs it can be a teadious job. I had fun working on terraform.
If you want to develop it further, I would suggest to remove the rundeck-api. And rebuild it the same way rundeck does. This way you can take the code from rundeck and parse/import it into terraform. Makes it testable and automatable :-)

And then for booleans to have an extra state.

  • true
  • false
  • not present

So in the interest of getting things moving along a little better; what can be done? Honestly while I've committed to TF, I've done so on a very small scale and would still take a lot of time to gear up on GOLANG to do things properly and contribute meaningful things. I'm still at the mercy of those who develop in GOLANG more frequently.

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.