Adding a block results in needless changes

Hi!

I have an existing digitalocean_app resource with a single service block in the spec, roughly like this:

resource "digitalocean_app" "production" {
  spec {
    name   = "production"
    region = "sfo"

    service {
      name               = "backend"
      build_command      = "yarn build"
      environment_slug   = "node-js"
      http_port          = 8080
      instance_count     = 1
      instance_size_slug = "basic-xxs"
      run_command        = "yarn start"
      source_dir         = "/backend"

      github {
        repo           = "[redacted]"
        branch         = "production"
        deploy_on_push = true
      }

      routes {
        path = "/api"
      }
    }
  }
}

and I’m trying to add another service into the spec by this change in the manifest:

resource "digitalocean_app" "production" {
  spec {
    name   = "production"
    region = "sfo"

+    service {
+      name = "frontend"
+      build_command      = "NEXT_PUBLIC_API_ENDPOINT=$URL npm run build"
+      environment_slug   = "node-js"
+      http_port          = 3000
+      instance_count     = 1
+      instance_size_slug = "basic-xxs"
+      run_command        = "npm run start"
+      source_dir         = "/frontend"
+
+      github {
+        repo           = "[redacted]"
+        branch         = "production"
+        deploy_on_push = true
+      }
+
+      routes {
+        path                 = "/"
+        preserve_path_prefix = false
+      }
+    }
+
    service {
      name               = "backend"
      build_command      = "yarn build"
      environment_slug   = "node-js"
      http_port          = 8080
      instance_count     = 1
      instance_size_slug = "basic-xxs"
      run_command        = "yarn start"
      source_dir         = "/backend"

      github {
        repo           = "[redacted]"
        branch         = "production"
        deploy_on_push = true
      }

      routes {
        path = "/api"
      }
    }
  }
}

However, when running a plan in the terraform cloud it wants to update the backend to turn in into frontend and to create a new backend service:

How can I force terraform to just add the frontend service and leave the backend intact?

Thanks!

Most of the time, including in this case, nested blocks (like service { } here) are considered to be elements of a list.

Here, the problem is that you have added a new item before existing items, so existing items get moved to different positions in the list.

To achieve the plan you are expecting, you will need to place the new service { } after all existing service { } blocks.

1 Like

@maxb aha, thanks for this clarification. I’d still like to keep this specific order to be consistent with other manifests in my codebase. Is there any way to achieve this? Eg. using multi-step plan&apply?

In cases like this, a lot depends on the individual Terraform provider implementation, and the API it is talking to.

I had a quick look at the provider source code, and it looks like it is just passing the services list through, maintaining order, to the DigitalOcean API, in a single API call.

At this point, the problem becomes less about how Terraform will behave, and more about how the DigitalOcean API will behave. The services field in the API appears to be a list … but does its concept of identity, for the purposes of applying updates, rely on the ordering of the list? Or on the name field, treating the list somewhat like a map? I do not know the answer to this.

If it turns out the API is happy for you to rearrange the order without downtime, you can just go ahead and apply your original Terraform plan. The plan as visualised by Terraform will appear misleading - but the plan is just Terraform’s attempt to visualise the difference between the start and end state, and it does not always pick the way that actually describes how the ultimate backend API will behave.

If, on the other hand, it turns out the API is going to undeploy and deploy your services if you rearrange the order, you’re a bit stuck, and will have to decide whether that’s worth it, to achieve consistency.

1 Like

@maxb Thanks so much for looking into this.

but does its concept of identity, for the purposes of applying updates, rely on the ordering of the list? Or on the name field, treating the list somewhat like a map? I do not know the answer to this.

Yeah, and I think no one can know except people with access to the DO’s API implementation.

I decided to go ahead and give it a try, and thankfully DO did the sane thing and just added the frontend service.

the plan as visualised by Terraform will appear misleading - but the plan is just Terraform’s attempt to visualise the difference between the start and end state, and it does not always pick the way that actually describes how the ultimate backend API will behave.

This is such a good insight that didn’t appear to me until now. It curbs my expectations I have from Terraform.

Thanks again!

@goodhoko This is a common behavior with some APIs. You can just let it apply whatever it wants. The end result will be the same.

E.g. adding a page_rule in Cloudflare in the middle of a list will cause it to show changes to all subsequent rules that it tries to shift up. It’s annoying yes but usually harmless.