siderolabs / terraform-provider-talos

Mozilla Public License 2.0
125 stars 17 forks source link

Error while applying patches #53

Closed artuross closed 1 year ago

artuross commented 1 year ago

Initial setup

main.tf:

terraform {
  required_providers {
    talos = {
      source  = "siderolabs/talos"
      version = "0.1.1"
    }
  }
}

provider "talos" {}

resource "talos_machine_secrets" "machine_secrets" {}

resource "talos_machine_configuration_controlplane" "control-planes" {
  cluster_name     = "test"
  cluster_endpoint = "https://127.0.0.1:6443"
  machine_secrets  = talos_machine_secrets.machine_secrets.machine_secrets

  config_patches = [
    #file("${path.module}/patch.json")
  ]
}

output "control-plane" {
  value     = talos_machine_configuration_controlplane.control-planes.machine_config
  sensitive = true
}

patch.json:

[
  {
    "op": "add",
    "path": "/machine/certSANs/0",
    "value": "localhost"
  }
]

Applying configuration

terraform init
terraform apply

After confirming, Terraform will fetch the provider and apply the configuration creating terraform.tfstate file. We can get the prepared YAML file with yq (we'll need this in a moment to confirm our patch is valid):

yq -r '.outputs.control-plane.value' terraform.tfstate > out.yaml

Let's uncomment the patch so that it is applied, below is the modified resource.

resource "talos_machine_configuration_controlplane" "control-planes" {
  cluster_name     = "test"
  cluster_endpoint = "https://127.0.0.1:6443"
  machine_secrets  = talos_machine_secrets.machine_secrets.machine_secrets

  config_patches = [
    file("${path.module}/patch.json")
  ]
}

If I try to apply now, I get an error:

❯ terraform apply
talos_machine_secrets.machine_secrets: Refreshing state... [id=SGSRIupW8fJT3CVXZMt8txjuGIw62Sz3o2vg4v7VqBU=]
talos_machine_configuration_controlplane.control-planes: Refreshing state... [id=test]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # talos_machine_configuration_controlplane.control-planes will be updated in-place
  ~ resource "talos_machine_configuration_controlplane" "control-planes" {
      ~ config_patches     = [
          + jsonencode(
                [
                  + {
                      + op    = "add"
                      + path  = "/machine/certSANs/0"
                      + value = "localhost"
                    },
                ]
            ),
        ]
        id                 = "test"
        # (8 unchanged attributes hidden)
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

talos_machine_configuration_controlplane.control-planes: Modifying... [id=test]
╷
│ Error: failure applying rfc6902 patches to talos machine config: add operation does not apply: doc is missing path: "/machine/certSANs/0": missing value
│ 
│   with talos_machine_configuration_controlplane.control-planes,
│   on main.tf line 14, in resource "talos_machine_configuration_controlplane" "control-planes":
│   14: resource "talos_machine_configuration_controlplane" "control-planes" {
│ 
╵

To confirm the patch is valid, let's attempt to patch the resource with talosctl command.

talosctl machineconfig patch out.yaml --patch @patch.json --output patched.yaml

As expected, we get a patched.yaml file with the patch applied.

JSON YAML format

patch.yaml:

- op: add
  path: /machine/certSANs/0
  value: localhost

main.tf:

resource "talos_machine_configuration_controlplane" "control-planes" {
  cluster_name     = "test"
  cluster_endpoint = "https://127.0.0.1:6443"
  machine_secrets  = talos_machine_secrets.machine_secrets.machine_secrets

  config_patches = [
    file("${path.module}/patch.yaml")
  ]
}

Same error

❯ terraform apply
talos_machine_secrets.machine_secrets: Refreshing state... [id=SGSRIupW8fJT3CVXZMt8txjuGIw62Sz3o2vg4v7VqBU=]
talos_machine_configuration_controlplane.control-planes: Refreshing state... [id=test]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # talos_machine_configuration_controlplane.control-planes will be updated in-place
  ~ resource "talos_machine_configuration_controlplane" "control-planes" {
      ~ config_patches     = [
          - jsonencode(
                [
                  - {
                      - op    = "add"
                      - path  = "/machine/certSANs/0"
                      - value = "localhost"
                    },
                ]
            ),
          + <<-EOT
                - op: add
                  path: /machine/certSANs/0
                  value: localhost
            EOT,
        ]
        id                 = "test"
        # (8 unchanged attributes hidden)
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

talos_machine_configuration_controlplane.control-planes: Modifying... [id=test]
╷
│ Error: failure applying rfc6902 patches to talos machine config: add operation does not apply: doc is missing path: "/machine/certSANs/0": missing value
│ 
│   with talos_machine_configuration_controlplane.control-planes,
│   on main.tf line 14, in resource "talos_machine_configuration_controlplane" "control-planes":
│   14: resource "talos_machine_configuration_controlplane" "control-planes" {
│ 
╵

Applying patch with talosctl works and produces identical file as the same command with patch.json.

talosctl machineconfig patch out.yaml --patch @patch.yaml --output patched.yaml

YAML strategic merge patch

patch_strategic.yaml:

machine:
  certSANs:
    - localhost

main.tf:

resource "talos_machine_configuration_controlplane" "control-planes" {
  cluster_name     = "test"
  cluster_endpoint = "https://127.0.0.1:6443"
  machine_secrets  = talos_machine_secrets.machine_secrets.machine_secrets

  config_patches = [
    file("${path.module}/patch_strategic.yaml")
  ]
}

Interestingly, this "works", however the patch has not been applied.

❯ terraform apply
talos_machine_secrets.machine_secrets: Refreshing state... [id=SGSRIupW8fJT3CVXZMt8txjuGIw62Sz3o2vg4v7VqBU=]
talos_machine_configuration_controlplane.control-planes: Refreshing state... [id=test]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # talos_machine_configuration_controlplane.control-planes will be updated in-place
  ~ resource "talos_machine_configuration_controlplane" "control-planes" {
      ~ config_patches     = [
          - <<-EOT
                - op: add
                  path: /machine/certSANs/0
                  value: localhost
            EOT,
          + <<-EOT
                machine:
                  certSANs:
                    - localhost
            EOT,
        ]
        id                 = "test"
        # (8 unchanged attributes hidden)
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

talos_machine_configuration_controlplane.control-planes: Modifying... [id=test]
talos_machine_configuration_controlplane.control-planes: Modifications complete after 0s [id=test]

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

Outputs:

control-plane = <sensitive>
yq -r '.outputs.control-plane.value' terraform.tfstate > out_patched.yaml

out.yaml and out_patched.yaml are identical. Once again, let's confirm that talosctl applies the patch correctly:

talosctl machineconfig patch out.yaml --patch @patch_strategic.yaml --output patched.yaml

My environment

❯ talosctl version
Client:
        Tag:         v1.3.5
        SHA:         03edf8c1
        Built:       
        Go version:  go1.19.6
        OS/Arch:     darwin/arm64

❯ terraform -version
Terraform v1.3.9
on darwin_arm64
+ provider registry.terraform.io/siderolabs/talos v0.1.1

❯ uname -a
Darwin iMac-Artur.local 22.3.0 Darwin Kernel Version 22.3.0: Mon Jan 30 20:39:35 PST 2023; root:xnu-8792.81.3~2/RELEASE_ARM64_T8103 arm64
frezbo commented 1 year ago

regarding the JSON patch, the merge happens in a different way (patch vs strategic), so it would be of something like this:

- op: add
  path: /machine/certSANs
  value:
    - localhost

and the second issue is something that's already reported, https://github.com/siderolabs/terraform-provider-talos/issues/45

it currently required a second terraform apply (haven't figured what;s the issue yet)

artuross commented 1 year ago

Yup, I'm aware of the difference between patch vs strategic - I simply tested all scenarios trying to find working configuration (and found that the strategic merge, at least in my case, does not work correctly either). In this particular example, both patches are equivalent in terms of the result (since initial certSANs is an empty array).

You are right that for strategic merge, 2nd terraform apply changes the output. Good catch!