Accessing .tfstate file in .tf file - terraform - azure

I want to create a new WebApp resource into existing resource group.
this question and this post explains how we can import existing resource ( instead of creating new one every time)
I was able to import my existing resource group using below command
terraform import azurerm_resource_group.rg-myResourceGroup /subscriptions/00000-my-subscription-id-0000000/resourceGroups/rg-myResourceGroup
After executing this command I can see new file is created named 'terraform.tfstate' Below is content of the file.
{
"version": 3,
"terraform_version": "0.11.11",
"serial": 1,
"lineage": "-----------------------------",
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {
"azurerm_resource_group.rg-ResourceGroupName": {
"type": "azurerm_resource_group",
"depends_on": [],
"primary": {
"id": "/subscriptions/subscription-id-00000000000/resourceGroups/rg-hemant",
"attributes": {
"id": "/subscriptions/subscription-id-00000000000/resourceGroups/rg-hemant",
"location": "australiaeast",
"name": "rg-ResourceGroupName",
"tags.%": "0"
},
"meta": {},
"tainted": false
},
"deposed": [],
"provider": "provider.azurerm"
}
},
"depends_on": []
}
]
}
Now my question is how can I access/refer/include terraform.tfstate in my main.tf
resource "azurerm_resource_group" "rg-hemant" {
#name = it should be rg-ResourceGroupName
#location = it should be australiaeast
}
UPDATE 1
Assume that in my subscription 'mysubscription1' there is a
resource group 'rg-exising'
This resource group already have few resources e.g. webapp1 ,
storageaccount1
Now I want to write a terraform script which will add new
resource ( e.g. newWebapp1 ) to existing resource group
'rg-existing'
so after terraform apply operation rg-exising should have
below resources
webapp1
storageaccount1
newWebapp1 ( added by new terraform apply script )
4) Note that I don't want terraform to create ( in case of apply ) OR delete ( in case of destroy ) my existing resources which belongs to rg-exising

you dont really, you just need to map your resource to the state in tfstate, so just do:
resource "azurerm_resource_group" "rg-hemant" {
name = 'rg-ResourceGroupName'
location = 'australiaeast'
}
and tf should recognize this resource as the one you have in the state file

Dug more through the posts and found a solution here.
We can use additional parameters to terraform destroy to specifically mentioned which resource we want to destroy
terraform destroy -target RESOURCE_TYPE.NAME -target RESOURCE_TYPE2.NAME
Note: What I have learnt is that, in this case there is no need to use terraform import command

Related

How does Terraform know that randomly named resources already exist?

Consider the following Terraform template:
terraform {
required_providers {
azurecaf = {
source = "aztfmod/azurecaf"
version = "1.2.23"
}
}
}
provider "azurerm" {
features {}
}
resource "azurecaf_name" "rg_name" {
name = var.appname
resource_type = "azurerm_resource_group"
prefixes = ["dev"]
suffixes = ["y", "z"]
random_length = 5 // <------ random part in name generation
clean_input = false
}
resource "azurerm_resource_group" "example" {
name = azurecaf_name.rg_name.result
location = var.resource_group_location
}
I applied this template, than ran terraform plan. Terraform plan tells me
No changes. Your infrastructure matches the configuration.
How does that work as azurecaf_name.rg_name contains random characters? I would have expected it to create a new resource group with a new (random) name. I know that Terraform keeps a state, but doesn't it execute the template every time (=new random name) and then checks if that matches state and real resources in cloud?
The random characters are stored in the state file along with the other attributes of the resource like random_length, prefixes, suffixes. When you rerun terraform it will first check have any of the attributes in your configuration changed from whats in the state file. In your case all the values like random_length are the same. So terraform knows it doesnt need to regenerate anything like the random attributes since your configuration has not changed. Terraform will then check the state of the resource in the remote provider using the resource ID generated by the remote provider. If it doesnt exist terraform will create it with the same data. if it does exist but its attributes dont match then it will update the resource. if it checks its values there match whats in the state file. If they are the same then terraform knows no changes are needed.
We can see this in the random_pet resource
resource "random_pet" "foo" {
length = 3
prefix = "foo"
}
State file
"resources": [
{
"mode": "managed",
"type": "random_pet",
"name": "foo",
"provider": "provider[\"registry.terraform.io/hashicorp/random\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "foo-evidently-secure-killdeer",
"keepers": null,
"length": 3,
"prefix": "foo",
"separator": "-"
},
"sensitive_attributes": []
}
]
}
],
Normally resources that have a form of randomness

how to access all elements of a list variable in the policy argument of aws_iam_user_policy resource in terraform

I have an aws_iam_user_policy resource in terraform as follows:
resource "aws_iam_user_policy" "pol" {
name = "policy"
user = aws_iam_user.singleuser.name
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:List*"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::toybucket-development/*",
"arn:aws:s3:::toybucket-staging/*",
"arn:aws:s3:::toybucket-production/*"
]
}
]
}
EOF
}
The resources with development, staging and production are something I'm hoping to put in one line through using a list variable with the values development, staging and production and somehow looping through them, but I'm unsure of how to do this within the EOF. I know normally you can loop through such list variable but that's in normal terraform and not when you have this EOF with a string that represents a json. Would anyone know of a solution?
You can do this most easily with a Terraform template, and the templatefile function. The templatefile function invocation would appear like:
resource "aws_iam_user_policy" "pol" {
name = "policy"
user = aws_iam_user.singleuser.name
policy = templatefile("${path.module}/policy.tmpl", { envs = ["development", "staging", "production"] }
}
The documentation for the function is probably helpful.
The template would appear like:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:List*"
],
"Effect": "Allow",
"Resource": [
%{~ for env in envs ~}
"arn:aws:s3:::toybucket-${env}/*"%{ if env != envs[length(envs) - 1] },%{ endif }
%{~ endfor ~}
]
}
]
}
That check at the end for adding a comma only if it is not the last element to ensure JSON format syntax is not super great. However, there is no easy check in Terraform DSL for whether a list/slice (latter being implicitly derived from Golang) is the last element, and using jsonencode would require placing the entire ARN in the variable list.
If envs = ["arn:aws:s3:::toybucket-development/*", "arn:aws:s3:::toybucket-staging/*", "arn:aws:s3:::toybucket-production/*"], then you could jsonencode(envs).
I'm a big fan of using the data source aws_iam_policy_document for creating all kind of iam policy like resources. In combination with a locals using the formatlist function you can achieve your desired result.
This could look like this:
locals {
resource_arns = formatlist("arn:aws:s3:::toybucket-%s/*", ["development", "staging", "production"])
}
data "aws_iam_policy_document" "policy" {
statement {
actions = ["s3:List*"]
effect = "Allow"
resources = [local.resource_arns]
}
}
resource "aws_iam_polcy" "pol" {
name = "policy"
user = aws_iam_user.singleuser.name
policy = data.aws_iam_policy_document.policy.json
}

azurerm_resource_group_template_deployment ignoring parameter file

I am attempting to use terraform and embedded ARM templates to permit creating a simple logic app in Azure. I have the resource block in terraform as:
resource "azurerm_resource_group_template_deployment" "templateTEST" {
name = "arm-Deployment"
resource_group_name = azurerm_resource_group.rg.name
deployment_mode = "Incremental"
template_content = file("${path.module}/arm/createLogicAppsTEST.json")
parameters_content = jsonencode({
logic_app_name = { value = "logic-${var.prefix}" }
})
}
and the createLogicAppsTEST.json file is defined (just the top few lines as)
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"logic_app_name": {
"defaultValue": "tsa-logic-dgtlbi-stage-001",
"type": "string"
}
},
"variables": {},
"resources": [
{
....
When deploying and running the first time, ie. creating the logic app resource using terraform and the embedded ARM template, it will create passing the name correctly given the:
parameters_content = jsonencode({
logic_app_name = { value = "logic-${var.prefix}" }
})
however, if I ever run again, terraform appears to ignore the parameters I am passing and goes with the default from the ARM template as:
"logic_app_name": {
"defaultValue": "tsa-logic-dgtlbi-stage-001",
"type": "string"
}
I have updated to the latest version of both terraform (0.14.2) and azurerm (2.40.0), yet the issue persists. At present, this kind of makes ARM in terraform problematic given different tiers, dev, test and prod at my company have different prefixes and names ie. prod-, dev-.
Is there a setting to make terraform actually use the parameters I am passing with azurerm_resource_group_template_deployment resource block?
After my validation, you could use the ignore_changes field in the nested block lifecycle. It will tell terraform ignore when planning updates to the associated remote object.
For example,
resource "azurerm_resource_group_template_deployment" "templateTEST" {
name = "arm-Deployment"
resource_group_name = azurerm_resource_group.rg.name
deployment_mode = "Incremental"
template_content = file("${path.module}/arm/createLogicAppsTEST.json")
parameters_content = jsonencode({
logic_app_name = { value = "logic-${var.prefix}" }
})
lifecycle {
ignore_changes = [template_content,]
}
}
However, in this case, it would be better to declare empty parameters without default values in the embedded ARM templates instead you can pass the real parameters via the parameters_content.
For example, declare the parameter like this in the ARM template. This will always use the content of the external parameters.
"logic_app_name": {
"type": "string"
}
I elected to just use the old provider, there is actually an open bug report about this same issue on github

Terraform asking for "ami" and "instance_type" after importing current state

I have an EC2 instance which is manually created on aws. I need to run a bash script inside my instance using terraform without recreating the EC2 instance.this is my tf file for this .
instance.tf
resource "aws_key_pair" "mykey" {
key_name = "mykey"
public_key = file(var.PUBLIC_KEY)
}
resource "aws_instance" "example" {
key_name = aws_key_pair.mykey.key_name
provisioner "file" {
source="script.sh"
destination="/tmp/script.sh"
}
connection {
type ="ssh"
user ="ubuntu"
private_key=file(var.PRIVATE_KEY)
host = coalesce(self.public_ip, self.private_ip)
}
}
vars.tf
variable "INSTANCE_USERNAME" {
default = "ubuntu"
}
variable "PUBLIC_KEY" {
default = "mykey.pub"
}
variable "PRIVATE_KEY" {
default ="mykey"
}
variable "AMIS" {}
variable "INSTANCE_TYPE" {}
provider.tf
provider "aws" {
access_key = "sd**********"
secret_key = "kubsd**********"
region = "us-east-2"
}
I have imported my current state using
terraform import aws_instance.example instance-id
This is my state file
{
"version": 4,
"terraform_version": "0.12.17",
"serial": 1,
"lineage": "54385313-09b6-bc71-7c9c-a3d82d1f7d2f",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "example",
"provider": "provider.aws",
"instances": [
{
"schema_version": 1,
"attributes": {
"ami": "ami-0d5d9d301c853a04a",
"arn": "arn:aws:ec2:us-east-2:148602461879:instance/i-054caec795bbbdf2d",
"associate_public_ip_address": true,
"availability_zone": "us-east-2c",
"cpu_core_count": 1,
"cpu_threads_per_core": 1,
"credit_specification": [
{
"cpu_credits": "standard"
}
],
continues...
But when i run terraform plan it is showing error like
Error: Missing required argument
on instance.tf line 5, in resource "aws_instance" "example":
5: resource "aws_instance" "example" {
The argument "ami" is required, but no definition was found.
Error: Missing required argument
on instance.tf line 5, in resource "aws_instance" "example":
5: resource "aws_instance" "example" {
The argument "instance_type" is required, but no definition was found.
I couldn't understand why it is asking for instance_type and ami . Its is present inside the terraform.tf state after importing my state . Do i need to pass this data manually ? Is there any way to automate this process?
The terraform import command exists to allow you to bypass the usual requirement that Terraform must be the one to create a remote object representing each resource in your configuration. You should think of it only as a way to tell Terraform that your resource "aws_instance" "example" block is connected to the remote object instance-id (using the placeholder you showed in your example). You still need to tell Terraform the desired configuration for that object, because part of Terraform's role is to notice when your configuration disagrees with the remote objects (via the state) and make a plan to correct it.
Your situation here is a good example of why Terraform doesn't just automatically write out the configuration for you in terraform import: you seem to want to set these arguments from input variables, but Terraform has no way to know that unless you write that out in the configuration:
resource "aws_instance" "example" {
# I'm not sure how you wanted to map the list of images to one
# here, so I'm just using the first element for example.
ami = var.AMIS[0]
instance_type = var.INSTANCE_TYPE
key_name = aws_key_pair.mykey.key_name
}
By writing that out explicitly, Terraform can see where the values for those arguments are supposed to come from. (Note that idiomatic Terraform style is for variables to have lower-case names like instance_type, not uppercase names like INSTANCE_TYPE.)
There is no way to use existing values from the state to populate the configuration because that is the reverse direction of Terraform's flow: creating a Terraform plan compares the configuration to the state and detects when the configuration is different, and then generates actions that will make the remote objects match the configuration. Using the values in the state to populate the configuration would defeat the object, because Terraform would never be able to detect any differences.

Provider information in azure to run command for azure portal?

I am unable to find information regarding azure provider information in ARM or using azure CLI?
I looked in the portal and google but none are providing the information?
I need the provider information so I can connect to Azure and run terraform deployment via azure terraform.
I want to place in a tf file. Can I separate the below into a single tf file and place other resources, such as the actual deployment of vnet, subnets, Iaas deployment, public IP, etc. all in separate tf files?
provider "azurerm" {
subscription_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_secret = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
tenant_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
resource "azurerm_resource_group" "myterraformgroup" {
name = "myResourceGroup"
location = "eastus"
tags = {
environment = "Terraform Demo"
}
}
I am trying to find the client_id and client_secret
Is id the same as subscription_id?
What is isDefault = True? What is the difference form is Default = False?
Can I assume the False is the free trial then the True is the actual pay as you go?
output appeared automatically when logged in from azure CLI:
[
{
"cloudName": "AzureCloud",
"id": "21eb90c5-a6ed-4819-a2d0-XXXXXXXXXXXXXX",
"isDefault": true,
"name": "Pay-As-You-Go",
"state": "Enabled",
"tenantId": "1d6cd91f-d633-4291-8eca-XXXXXXXXXXX",
"user": {
"name": "samename01#yahoo.com",
"type": "user"
}
},
{
"cloudName": "AzureCloud",
"id": "b6d5b1ee-7327-42a0-b8e3-XXXXXXXXXXXXXX",
"isDefault": false,
"name": "Pay-As-You-Go",
"state": "Enabled",
"tenantId": "1d6cd91f-d633-4291-8eca-XXXXXXXXXXXX",
"user": {
"name": "samename01#yahoo.com",
"type": "user"
}
}
]
Can I separate the below into a single tf file and place other resources such as the actual deployment of vnet, subnets, Iaas deployment, public IP,... all in separate tf files?
Yes, you can separate Terraform configuration out into as many files as you'd like, as long as those files are in the same directory. So in your case it sounds like you might want a file for each resource (providers.tf, vnet.tf, subnet.tf, etc.)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX I am trying to find the "client_id" and "client_secret"? Is "id" the same as subscription_id? What is isDefault = True? What is the difference form is Default = False? Can I assume the False is the free trial then the True is the actual pay as you go?
This has nothing to do with billing and payments, you can just ignore the isDefault flag since it's not important. What is important is the subscription id, a basic Terraform configuration will authenticate against 1 Azure subscription, and billing/payments in Azure are at the subscription level.
client_id is the application id of your service principal which you will use to authenticate Terraform in Azure. This is the instructions for setting that up, it's fairly simple.

Resources