How can one destroy terraform configuration in Azure efficiently? - azure

I have an Azure terraform configuration. It sets up resource groups, key vaults, passwords, etc ...
When I destroy it terraform does the same in reverse - deleting secrets, access polices, key vaults and the last are resource groups.
But, if the resource groups are to be destroyed anyway, it makes sense just to destroy them first - all the child resources will be deleted automatically. But the azurerm provider does not do it this way.
What am I missing here? And if my understanding is correct, is there a way to implement it (without altering the provider, that is) ?

In Terraform's model, each resource is distinct. Although Terraform can see the dependencies you've defined or implied between them, it doesn't actually understand that e.g. a key vault is a child object of a resource group and so the key vault might be deleted as a side-effect of deleting the resource group.
With that said, unfortunately there is no built-in way in Terraform today to achieve the result you are looking for.
A manual approximation of the idea would be to use terraform state rm to tell Terraform to "forget" about each of the objects (that is, they will still exist in Azure but Terraform will have no record of them) that will eventually be destroyed as a side-effect of deleting the resource group anyway, and then running terraform destroy will only delete the resource group, because Terraform will believe that none of the other objects exist yet anyway. However, that is of course a very manual approach that, without some careful scripting, would likely take longer than just letting the Azure provider work through all of the objects in dependency order.
There is an exploratory issue in the Terraform repository that covers this use-case (disclaimer: I'm the author of that issue), but the Terraform team isn't actively working on that at the time I write this, because efforts are focused elsewhere. The current set of use-cases captured there doesn't quite capture your idea here of having Terraform recognize when it can skip certain destroy operations, so you might choose to share some details about your use-case on that issue to help inform potential future design efforts.

Terraform is built this way, it wouldn't traverse the graph and understand that if the resource group is deleted - anything inside resource group will be deleted as well. which isn't even true in some cases. So I would say it doesn't make sense to do that.
Only real time when this is annoying - when you are testing. for that time you can create a script that would initiate resource group deletion and clear local state, for example

Related

Does terraform guarantee that if no changes were reported by plan, it will be able to recreate resources the same way they currently are?

I have a lot of resources in my Azure subscription. I want to manage them with terraform, so I want to import them using terraform import. I import every resource manually one-by-one. Then I run terraform plan and check that there are no changes to be made reported i.e. that the current infrastructure matches the configuration.
Does this mean that if I were to manually delete some of the resources via Azure portal or cli, I would be able to recreate them wit terraform apply perfectly so that they would have exactly the same configuration as before and would operate in exactly the same way?
In general Terraform cannot guarantee that destroying an object and recreating it will produce an exactly equivalent object.
It is possible for that to work, but it requires a number of things to be true, including:
Your configuration specifies the values for resource arguments exactly as they are in the remote API. For example, if a particular resource type has a case-insensitive (but case-preserving) name then a provider will typically ignore differences in case when planning changes but it will use exactly the case you wrote in the configuration, potentially selecting a different name.
The resource type does not include any "write-only" arguments. Some resource types have arguments that are used only by the provider itself and so they don't get saved as part of the object in the remote API even though they are saved in the Terraform state. terraform import therefore cannot re-populate those into the state, because there is nowhere else to read them from except the Terraform state.
The provider doesn't have any situations where it treats an omitted argument as "ignore the value in the remote system" instead of "unset the value in the remote system". Some providers make special exceptions for certain arguments where leaving them unset allows them to "drift" in the remote API without Terraform repairing them, but if you are using any resource types which behave in that way then the value stored in the remote system will be lost when you delete the remote object and Terraform won't be able to restore that value because it's not represented in your Terraform configuration.
The hashicorp/azurerm provider in particular has many examples of situation 3 in the above list. For example, if you have an azurerm_virtual_network resource which does not include any subnet blocks then the provider will not propose to delete any existing subnets, even though the configuration says that there should be no subnets. However, if you delete the virtual network and then ask Terraform to recreate it then the Terraform configuration has no record of what subnets were supposed to exist and so it will propose to create a network with no subnets at all.

Prevent resources from being deleted via Terraform/ Azure Console in a large team

Current situation: We are at the beginning of a migration to the Azure Cloud. It is an enterprise project with many services. There are several people on the team who have little experience with Terraform or Azure.
Goals:
In best case, all resources of the production Azure subscription are managed with Terraform so that changes can be easily tracked and a new empty subscription (e.g. test subscription) can be quickly brought up to the same level. All with as few manual steps as possible.
In my absence, changes can also be made by inexperienced team members, but it should be prevented that certain resources are accidentally deleted. In addition, this should also apply to me, since I have not been doing the whole thing for so long and mistakes can always happen. I would like to include several hurdles here to avoid accidental deletion of some resources both on Terraform and in the Azure Gui.
What I have tried:
1.
lifecycle {
prevent_destroy = true
}
This prevents deletion using "Terraform destroy" but not, for example, deletion of the resource if someone deletes it from the tf file or deletes the entire ts file and then does "Terraform apply" and then oversees the deletion. It also does not prevent deletion in the Azure Gui.
Using azurerm_management_lock
E.g. resource1.tf
resource "azurerm_resource_group" "rgtest1" {
name = "rgtest1"
location = "westeurope"
lifecycle {
prevent_destroy = true
}
}
resource "azurerm_management_lock" "resource-group-level" {
name = "resource-group-level"
scope = azurerm_resource_group.rgtest1.id
lock_level = "CanNotDelete"
notes = "This Resource Group should not be deleted"
}
Again, doing a general "terraform apply" will not prevent the deletion of the resource if the file is accidentally deleted. Is it possible to keep the lock configuration in another terraform workspace where both workspaces have the same backend and therefore the same terraform state terraform.tfstate?
Manually setting the resource lock in the Azure Gui/Console
Since it may be multiple resources and I wanted to keep it as simple as possible, I don't feel this is a really good solution.
terraform state rm
I don't like the solution because it should be possible to change the locked resources with Terraform sometimes. In addition, these would be manual commands. It would be better to have this within the Terraform files. Is this possible?
Question:
I must apologize for the complexity of the question. Does anyone have a suggestion in which direction I should go to efficiently prevent accidental deletion (even with owner rights) in a team? Maybe a good "Four eyes principle"?

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.

Resources