Cannot taint null_resource - terraform

I got terraform 0.11.11.
The graph show that the resource in speaking is in root module
$ terraform graph
digraph {
compound = "true"
newrank = "true"
subgraph "root" {
"[root] data.template_file.default" [label = "data.template_file.default", shape = "box"]
"[root] data.template_file.etcd" [label =
...
"[root] null_resource.service_resolv_conf" [label = "null_resource.service_resolv_conf", shape = "box"]
...
But the trying to taint it says it is not:
$ terraform taint null_resource.service_resolv_conf
The resource null_resource.service_resolv_conf couldn't be found in the module root.
updates
$ terraform state list|grep resolv_conf
null_resource.service_resolv_conf[0]
null_resource.service_resolv_conf[1]
then i tried:
$ terraform taint null_resource.service_resolv_conf[0]
The resource null_resource.service_resolv_conf[0] couldn't be found in the module root.
and
$ terraform taint null_resource.service_resolv_conf
The resource null_resource.service_resolv_conf couldn't be found in the module root.

terraform graph gives your the whole picture about the resources and their relationship.
But it is not a good command for troubleshooting and understand how the resources are named in terraform *.tfstate file.
I would recommend to run with terraform state list, then you can easily know how to taint one of the resources in list.
terraform state list
terraform taint <copy resource directly from above list>

For whoever who comes into this thread looking for terraform taint/untaint null_resource where terraform errors out with The resource […] couldn't be found in the module root here's the correct and working answer posted by #victor-m at Cannot taint null_resource
terraform taint -module=name null_resource.name
Same for untaint command.

After all I found out the solution
It appears, then when there are more hosts in connection based on list (used 'count')
resource "null_resource" "provision_docker_registry" {
count = "${length(local.service_vms_names)}"
depends_on = ["null_resource.service_resolv_conf"]
connection {
user = "cloud-user"
private_key = "${file("${path.module}/../ssh/${var.os_keypair}.pem")}"
host = "${element(local.service_fip, count.index)}"
}
You taint the resource by specifying index after dot, i.e.
$ terraform taint null_resource.provision_docker_registry.0
The resource null_resource.provision_docker_registry.0 in the module root has been marked as tainted!
Voila!
I could have not found that in documentation. Hope this helps someone.

Related

Terraform Provider config inside child module

I’m trying to create modules that will handle helm deployments. the structure goes like this
root module - call helm-traefik module
there’s an input (cluster name) that will be used to fetch data sources for the provider config inside the helm child module.
child module - helm-traefik
main tf. - call helm module
variables.tf
values.yaml
child module - helm
providers.tf - both provider config for kubernetes and helm are using kubelogin for authentication
datasources.tf
main.tf - helm_release
variables.tf
The issue is that I’m getting an error with tf plan and it says that Kubernetes cluster is unreachable. I’ve been reading docs regarding providers and I think the reason why I’m getting errors is that I don’t have the provider config for Kubernetes and helm in the root module level. Any feasible solution for this use case? I want to have a separation between the helm module in a way it can be consumed regardless of the helm chart to be deployed.
Also, If I put the provider config from the child module to the root module, that would mean I need to create a provider config for each cluster I want to manage.
on helm - child module, this is how I generate the provider config
datasources.tf
locals {
# The purpose of the cluster.
purpose = split("-", "${var.cluster_name}")[0]
# The network environment of the cluster.
customer_env = split("-", "${var.cluster_name}")[2]
}
data "azurerm_kubernetes_cluster" "cluster" {
resource_group_name = "rg-${local.purpose}-${local.customer_env}-001"
name = "aks-${local.purpose}-${local.customer_env}"
}
provider.tf
provider "kubernetes" {
host = data.azurerm_kubernetes_cluster.cluster.kube_config.0.host
cluster_ca_certificate = base64decode(data.azurerm_kubernetes_cluster.cluster.kube_config.0.cluster_ca_certificate)
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "kubelogin"
args = [
"get-token",
"--login", "spn",
"--environment", "AzurePublicCloud",
"--server-id", "6dae42f8-4368-4678-94ff-3960e28e3630",
"--tenant-id", data.azurerm_client_config.current.tenant_id,
"--client-id", data.azurerm_client_config.current.client_id,
"--client-secret", data.azurerm_key_vault_secret.service_principal_key.value,
]
}
}

Azure AKS with agic - how to create it with Terraform?

I'm currently setting up an AGIC (Kubernetes Application Gateway Ingress Controller) for an AKS environment (https://azure.github.io/application-gateway-kubernetes-ingress/setup/install-existing/#using-a-service-principal).
As the whole environment is setup with Terraform, I'd like to install the necessary Helm repository also with Terraform.
Thought, the following simple code should do the trick:
data "helm_repository" "agic_repo" {
name = "agic_repository"
url = "https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/"
}
resource "helm_release" "agic" {
name = "agic"
namespace = "agic"
repository = data.helm_repository.agic_repo.metadata[0].url
chart = "application-gateway-kubernetes-ingress"
depends_on = [
data.helm_repository.agic_repo,
]
}
But I ran into this issue:
module.agic.helm_release.agic: Creating...
Error: chart "application-gateway-kubernetes-ingress" not found in https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/ repository
on ../../modules/agic/main.tf line 91, in resource "helm_release" "agic":
91: resource "helm_release" "agic" {
So it looks, as if the package cannot be found. Did anyone else solve this before?
I'm not familiar with Helm, so I don't know how to 'browse' within the Helm repos to check whether I'm addressing the right URI...
So I added the repo manually with
helm repo add application-gateway-kubernetes-ingress https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/
When I search for the repo I receive:
V5T:~$ helm search | grep ingress
application-gateway-kubernetes-ingress/ingress-azure 1.0.0 1.0.0 Use Azure Application Gateway as the ingress for an Azure...
Any help appreciated!
P.S.: Sure, I could do it with a bash one-liner, but would be great to have the whole environment created by Terraform...
According to the data you provided it has to be this:
resource "helm_release" "agic" {
name = "agic"
namespace = "agic"
repository = data.helm_repository.agic_repo.metadata[0].url
chart = "ingress-azure"
depends_on = [
data.helm_repository.agic_repo,
]
}
so the chart name is different

Terraform long term lock

Using Terraform 0.12 with the remote state in an S3 bucket with DynamoDB locking.
It seems that a common pattern for Terraforming automation goes more or less like this:
terraform plan -out=plan
[review plan]
terraform apply plan
But then, maybe I'm overlooking something obvious, there's no guarantee other terraform apply invocations haven't updated the infrastructure between 1 and 3 above.
I know locking will prevent a concurrent run of terraform apply while another one is running (and locking is enabled) but can I programmatically grab a "long term locking" so the effective workflow looks like this?
[something to the effect of...] "terraform lock"
terraform plan -out=plan
[review plan]
terraform apply plan
[something to the effect of...] "terraform release lock"
Are there any other means to "protect" infrastructure from concurrent/interdependant updates that I'm overlooking?
You don't need this as long as you are only worrying about the state file changing.
If you provide a plan output file to apply and the state has changed since then Terraform will error before making any changes, complaining that the saved plan is stale.
As an example:
$ cat <<'EOF' >> main.tf
> resource "random_pet" "bucket_suffix" {}
>
> resource "aws_s3_bucket" "example" {
> bucket = "example-${random_pet.bucket_suffix.id}"
> acl = "private"
>
> tags = {
> ThingToChange = "foo"
> }
> }
> EOF
$ terraform init
# ...
$ terraform apply
# ...
$ $ sed -i 's/foo/bar/' main.tf
$ terraform plan -out=plan
# ...
$ sed -i 's/bar/baz/' main.tf
$ terraform apply
# ...
$ terraform apply plan
Error: Saved plan is stale
The given plan file can no longer be applied because the state was changed by
another operation after the plan was created.
What it won't do is fail if something outside of Terraform has changed anything. So if instead of applying Terraform again with baz as the tag for the bucket I had changed the tag on the bucket via the AWS CLI or the AWS console then Terraform would have happily changed it back to bar on the apply with the stale plan.

How to set hostname with cloud-init and Terraform?

I am starting with Terraform. I am trying to make it set a friendly hostname, instead of the usual ip-10.10.10.10 that AWS uses. However, I haven't found how to do it.
I tried using provisioners, like this:
provisioner "local-exec" {
command = "sudo hostnamectl set-hostname friendly.example.com"
}
But that doesn't work, the hostname is not changed.
So now, I'm trying this:
resource "aws_instance" "example" {
ami = "ami-XXXXXXXX"
instance_type = "t2.micro"
tags = {
Name = "friendly.example.com"
}
user_data = "${data.template_file.user_data.rendered}"
}
data "template_file" "user_data" {
template = "${file("user-data.conf")}"
vars {
hostname = "${aws_instance.example.tags.Name}"
}
}
And in user-data.conf I have a line to use the variable, like so:
hostname = ${hostname}
But this gives me a cycle dependency:
$ terraform apply
Error: Error asking for user input: 1 error(s) occurred:
* Cycle: aws_instance.example, data.template_file.user_data
Plus, that would mean I have to create a different user_data resource for each instance, which seems a bit like a pain. Can you not reuse them? That should be the purpose of templates, right?
I must be missing something, but I can't find the answer.
Thanks.
Using a Terraform provisioner with the local-exec block will execute it on the device from which Terraform is applying: documentation. Note specifically the line:
This invokes a process on the machine running Terraform, not on the resource. See the remote-exec provisioner to run commands on the resource.
Therefore, switching the provisioner from a local-exec to a remote-exec:
provisioner "remote-exec" {
inline = ["sudo hostnamectl set-hostname friendly.example.com"]
}
should fix your issue with setting the hostname.
Since you are supplying the tag to the instance as a string, why not just make that a var?
Replace the string friendly.example.com with ${var.instance-name} in your instance resource and in your data template. Then set the var:
variable "instance-name" {
default="friendly.example.com"
}
I believe that your user-data.conf should be bash script, to start with #!/usr/bin/env bash.
It should look like
#!/usr/bin/env bash
hostname ${hostname}

terraform: How to destroy oldest instance when lowering aws_instance count

Given a pair of aws instances deployed with
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "example" {
count = 2
ami = "ami-2757f631"
instance_type = "t2.micro"
tags = {
Name = "Test${count.index}"
}
}
Lowering count = 1 will destroy the last instance deployed:
Terraform will perform the following actions:
- aws_instance.example[1]
Is it possible to get terraform to destroy the first instance. ie.
Terraform will perform the following actions:
- aws_instance.example[0]
Terraform is tracking which instance is which via its state. When you reduce your count on the aws_instance resource Terraform will simply remove the later instances. While this shouldn't really be much of an issue because I would only really recommend that you are deploying groups of homogenous instances that can handle the load being interrupted (and would sit behind some form of load balancer mechanism) if you really needed to you could edit the state file to reorder the instances before reducing the number of instances.
The state file is serialised as JSON so you can just edit it directly (making sure it's uploaded to whatever you're using for remote state if you are using remote state) or better yet you can use the first class tools for editing remote state that the Terraform CLI provides with terraform state mv.
As an example you can do this:
# Example from question has been applied already
# `count` is edited from 2 to 1
$ terraform plan
...
aws_instance.example[1]: Refreshing state... (ID: i-0c227dfbfc72fb0cd)
aws_instance.example: Refreshing state... (ID: i-095fd3fdf86ce8254)
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
- aws_instance.example[1]
Plan: 0 to add, 0 to change, 1 to destroy.
...
$
$
$
$ terraform state list
aws_instance.example[0]
aws_instance.example[1]
$
$
$
$ terraform state mv aws_instance.example[1] aws_instance.example[2]
Moved aws_instance.example[1] to aws_instance.example[2]
$ terraform state mv aws_instance.example[0] aws_instance.example[1]
Moved aws_instance.example[0] to aws_instance.example[1]
$ terraform state mv aws_instance.example[2] aws_instance.example[0]
Moved aws_instance.example[2] to aws_instance.example[0]
$
$
$
$ terraform plan
...
aws_instance.example[1]: Refreshing state... (ID: i-095fd3fdf86ce8254)
aws_instance.example: Refreshing state... (ID: i-0c227dfbfc72fb0cd)
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
~ update in-place
- destroy
Terraform will perform the following actions:
~ aws_instance.example
tags.Name: "Test1" => "Test0"
- aws_instance.example[1]
Plan: 0 to add, 1 to change, 1 to destroy.
...

Resources