Terraform state sync with external changes - azure

I have terraform managing my infrastructure in Azure. However, there are cases where the state can get out of sync when other services are changing the infrastructure as well.
For example, I have terraform create an Application Gateway. But I also have an AKS cluster with AGIC enabled, which dynamically updates/changes rules, listeners, etc. inside of Application Gateway. So if terraform is re-run after AGIC makes some changes, terraform doesn't know and wants to reset to the default config it knows about.
Maybe this isn't possible, but is there an automated way to sync the two? It's kind of unfeasible to have to go into the terraform config and manually add the changes AGIC makes every time it does so. At this point, is it even worth managing Application Gateway with terraform?

If you really want to create the Application Gateway with Terraform, you should probably use the Lifecycle Meta-Argument ignore_changes on the attributes that will be modified by AKS. It is not perfect, but at least they can share their responsibilities without overwriting each other.
The ignore_changes feature is intended to be used when a resource is
created with references to data that may change in the future, but
should not affect said resource after its creation. In some rare
cases, settings of a remote object are modified by processes outside
of Terraform, which Terraform would then attempt to "fix" on the next
run. In order to make Terraform share management responsibilities of a
single object with a separate process, the ignore_changes
meta-argument specifies resource attributes that Terraform should
ignore when planning updates to the associated remote object.
For example :
resource "azurerm_application_gateway" "example" {
...
lifecycle {
ignore_changes = [
http_listener,
request_routing_rule,
backend_http_settings
]
}
...
}

Related

Terraform AzureRM Continually Modifying API Management with Proxy Configuration for Default Endpoint

We are terraforming our Azure API Management instance.
...
resource "azurerm_api_management" "apim" {
name = "the-apim"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
...
hostname_configuration {
proxy {
host_name = "the-apim.azure-api.net"
negotiate_client_certificate = true
}
}
}
...
We need to include the hostname_configuration block so that we can switch negotiate_client_certificate to true for the default endpoint.
This does the job, however every time Terraform runs it plans to modify the APIM instance by adding the hostname_configuration block again:
+ hostname_configuration {
+ proxy {
+ host_name = "the-apim.azure-api.net"
+ negotiate_client_certificate = true
}
}
Is there a way to prevent this from happening? In the portal I can see this value is set to true.
I suggest you try to pair with lifecycle > ignore_changes.
The ignore_changes feature is intended to be used when a resource is created with references to data that may change in the future, but should not affect said resource after its creation. In some rare cases, settings of a remote object are modified by processes outside of Terraform, which Terraform would then attempt to "fix" on the next run. In order to make Terraform share management responsibilities of a single object with a separate process, the ignore_changes meta-argument specifies resource attributes that Terraform should ignore when planning updates to the associated remote object.
In your case, the hostname_configuration is considered a "nested block" or "attribute as block" in Terraform. So the usage of ignore_changes is not so straightforward (you can't just add the property name, as you would do if you wanted to ignore changes in your resource_group_name for example, which is directly a property). From an issue in GitHub back from 2018, it seems you could use the TypeSet hash of the nested block to add to an ignore sections.
Even though I can't test this, my suggestion for you:
deploy your azurerm_api_management resource normally with the hostname_configuration block
check the state file from your resource and get the typeset hash of the hostname_configuration part; should be similar to hostname_configuration.XXXXXX
add an ignore_changes section passing the above
resource "azurerm_api_management" "apim" {
# ...
lifecycle {
ignore_changes = [
"hostname_configuration.XXXXXX",
]
}
}
Sometimes such issues occur due to issues in the provider. Probably it is not storing the configuration in the state file or not retrieving the stored state for this block. Try upgrading the provider to the latest available provider and see if it sorts the issue.
If that does not solve it, you can try defining this configuration as a separate resource. As per the terraform documentation: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/api_management
It's possible to define Custom Domains both within the
azurerm_api_management resource via the hostname_configurations block
and by using the azurerm_api_management_custom_domain resource.
However it's not possible to use both methods to manage Custom Domains
within an API Management Service, since there'll be conflicts.
So Please try removing that hostname_configuration block and add it as separate resource as per this documentation: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/api_management_custom_domain
This will most likely fix the issue.

How to Conditionally Create an Azure Resource-Group (or Any Resource) if Someone Else Has Not Created One

I know that when I do terraform apply it does not deploy a resource if the previous deployment within the same terraform state, it would not re-create it .
But I want to do something different:
Create a resource if it is not created by someone else.
But if the resource is already there and even it is not in the terraform state, do not generate an error and have refrence to its name.
Is there any known pattern to do this?
By design Terraform providers will typically not automatically "adopt" existing objects as now being managed by Terraform, because to do so would potentially lead to costly mistakes if you inadvertently bind a remote object to a Terraform resource and then run terraform destroy without realizing what is going to be destroyed.
Instead, you must bind existing objects to your Terraform resources using the terraform import command, telling Terraform explicitly that you intend it to become the sole manager of that object.

What is the purpose of an import in Terraform?

This question is not how to import and it's not what's the purpose of tfstate. It's what's the purpose of importing a pre-existing resource, esp. compared to just referencing the ID of the existing resource?
Terraform has the feature of terraform import. HashiCorp describes the purpose of this as:
Terraform is able to import existing infrastructure. This allows you take resources you've created by some other means and bring it under Terraform management.
This is a great way to slowly transition infrastructure to Terraform, or to be able to be confident that you can use Terraform in the future if it potentially doesn't support every feature you need today.
I read the article about the purpose of Terraform state. It does make sense to me to track Terraform state with .tfstate files when those files are mappings back to the configurations in .tf files.
But it's still unclear to me what the purpose of a standalone .tfstate file is when it only maps to an empty resource block. If there is a resource not in terraform yet, I would typically do one of two things:
put the resource in terraform, tear down the resource manually and re-deploy the resource with terraform, or...
keep the resource un-templated, reference its resource ID as a parameter and get its metadata via a data element for terraform-managed resources that rely on it.
Is terraform import an alternative to those two approaches? And if so, why would you use that approach?
The only way to make changes to an imported resource (that only has an empty resource block in the .tf file and detailed state in .tfstate) is to make manual changes and then re-import into .tfstate`, right? And if so, then what's the point of tracking the state of that resource in terraform?
I'm sure there's a good reasons. Just want to understand this deeper! Thanks!
But it's still unclear to me what the purpose of a standalone .tfstate
file is when it only maps to an empty resource block.
You wouldn't use a standalone .tfstate file. You would be using the same .tfstate file that all your other resources are in.
If there is a resource not in terraform yet, I would typically do one
of two things:
put the resource in terraform, tear down the resource manually and re-deploy the resource with terraform, or...
keep the resource un-templated, reference its resource ID as a parameter and get its metadata via a data element for
terraform-managed resources that rely on it.
Is terraform import an alternative to those two approaches? And if so,
why would you use that approach?
Consider the case where you have a production database with terrabytes of data already load in it, and users actively performing actions that query that database 24 hours a day. Your option 1 would require some down time, possibly a lot of down time, because you would have to deal with backing up and restoring terrabytes of data. Your option 2 would never let you manage changes to your database server via Terraform. That's what the Terraform import feature solves. It lets Terraform take "full control" of resources that already exist, without having to recreate them.
I agree that if a system outage is not an issue, and if recreating a resource isn't going to take much time, using option 1 is the way to go. Option 2 is only for resources that you never want to fully manage in Terraform, which is really a separate issue from the one Terraform import solves.
When importing a resource with terraform import it is necessary to write the configuration block to manage it with Terraform. On the same page you linked it states:
The current implementation of Terraform import can only import resources into the state. It does not generate configuration. A future version of Terraform will also generate configuration.
Because of this, prior to running terraform import it is necessary to
write manually a resource configuration block for the resource, to
which the imported object will be mapped.
So to bring preexisting resources under Terraform management, you first write the resource block for it in a .tf file. Next you use terraform import to map the resource to this resource block in your .tfstate. The next time you run terraform plan, Terraform will determine what changes (if any) will need to be made upon the next terraform apply based on the resource block and the actual state of the resource.
EDIT
The "why" of terraform import is to manage resources that are previously unknown to Terraform. As you alluded to in your second bullet point, if you want metadata from a resource but do not want to change the configuration of the resource, you would use a data block and reference that in dependent resources.
When you want to manage the configuration of a resource that was provisioned outside of Terraform you use terraform import. If you tear down the resource there may be data loss or service downtime until you re-deploy with Terraform, but if you use terraform import the resource will be preserved.
The import process can be started with an empty resource block, but the attributes need to be filled out to describe the resource. You will get the benefits of terraform plan after importing, which can help you find the discrepancies between the resource block and the actual state of the resource. Once the two match up, you can continue to make additional changes to the resource like any other resource in Terraform.
Terraform state file is your source of truth for your cloud infrastructure. Terraform uses local state to create plans and make changes to the infrastructure. Before any terraform operation, terraform does a refresh to update the state with the real infrastructure.

Terraform resource with the ID already exists

When Terraform run task executes in azure devops release pipeline I get an error "A resource with the ID already exists".
The resource exists in Azure but why it is complaining about the resource if this already exists. This should ignore this part. Please help what I need to add in my code that will fix this error!
Am I just using this bugging terraform tool for deploying azure resource? Terraform help is terrible!!!
resource "azurerm_resource_group" "test_project" {
name = "${var.project_name}-${var.environment}-rg"
location = "${var.location}"
tags = {
application = "${var.project_name}"
}
}
Terraform is designed to allow you to manage only a subset of your infrastructure with a particular Terraform configuration, in case either some objects are managed by another tool or in case you've decomposed your infrastructure to be managed by many separate configurations that cooperate to produce the desired result.
As part of that design, Terraform makes a distinction between an object existing in the remote system and that object being managed by the current Terraform configuration. Where technical constraints of an underlying API allow it, Terraform providers will avoid implicitly taking ownership of something that was not created by that specific Terraform configuration. The error message you saw here is the Azure provider's implementation of that, where it pre-checks to make sure the name you give it is unique so that it won't overwrite (and thus take implicit ownership of) an object created elsewhere.
To proceed here you have two main options, depending on your intended goal:
If this object was formerly managed by some other system and you now want to manage it exclusively with this Terraform configuration, you can tell Terraform to associate the existing object with the resource block you've written and thus behave as if that object were originally created by that resource block:
terraform import azurerm_resource_group.test_project /subscriptions/YOUR-SUBSCRIPTION-ID/resourceGroups/PROJECTNAME-ENVIRONMENTNAME-rg
After you run terraform import you must ensure that whatever was previously managing that object will no longer associate with it. This object is now owned by this Terraform configuration and must not be changed by any other system.
If this object is managed by some other system and you wish to continue managing it that way then you can instead use a data block to retrieve information about that existing object to use elsewhere in your configuration without Terraform taking ownership:
data "azurerm_resource_group" "example" {
name = "${var.project_name}-${var.environment}-rg"
}
If you needed the resource group's location name elsewhere in your module, for example, you could use data.azurerm_resource_group.example.location to access it. If you wanted to make any later changes to this resource group, you would continue to do that using whichever other system is considered the owner of it in your environment.
The main difference between these two approaches is how Terraform will record the object in state snapshots. terraform import causes Terraform to create a binding between the resource configuration you wrote and the remote object whose id you gave on the command line, which is henceforth indistinguishable to Terraform from it having created that object and recorded the binding itself in the first place. For a data resource, Terraform just reads the data about the existing object and saves a cache of it in the state so it can determine if the value has changed on a future run; it will never plan to make any modifications to an object used with a data block.
Try to delete the .terraform local folder to clean the cache, then run terraform init again and retry running the pipeline.
For my future self:
Today I stumbled across this same problem, because I renamed some resources, and terraform could not track them. I found out about terraform state mv ... which gives you the ability to rename resources in your state file, so that it can track remote resources. Really useful.

Terraform persistent and dynamic infrastructure parts?

I want to divide my infrastructure into two parts:
Persistent (firewalls, block storages, etc)
Dynamic (that will consume persistent resources from #1)
I want to be sure that persistent part never would be deleted and at the same time, there would be an option terraform destroy on the dynamic infrastructure part.
All resources you do not want to destroy you have to add the lifecycle policy: prevent_destroy
Have a look at the documenation: https://www.terraform.io/docs/configuration/resources.html#prevent_destroy
To fully prevent the destruction you would have to fine tune the permissions on the resources at your provider. However there is an easy way to divide your infrastructure.
Terraform offers a remote state data source which allows you to use output from a different project so you won't be able to destroy those resources while working with the dynamic part.
I have a bit of a different work around. The resources I do not want to delete with "terraform destroy" I create as "null_resource" using a provisioner with CLI. You can still use your variables in terraform as well.
for example (Create a resource group, but it is persistent due to null_resource)
resource "null_resource" "backend-config" {
provisioner "local-exec" {
command = <<EOT
az group create --location ${var.Location} --name ${var.Resource_group_name} --tags 'LineOfBusiness=${var.Lob}' 'Region=${var.Region}' 'Purpose="Terraform-Primary-Resource-Group-${var.Lob}'
EOT
interpreter = ["Powershell", "-Command"]
}
}
Now if you destroy the resources using terraform destroy. Any null_resource will remain intact.
I would solve this by having 2 Terraform deployments. You create the "static" resources once, and don't touch them. For extra safety, manually add a deletion lock to those resources (eg. I know you can do this in Azure, I assume other cloud providers have a similar solution).
Import these resources in your Dynamic Terraform deployment, using data blocks (not resources). Terraform will never attempt to delete resources you import using data blocks.

Resources