getting an error while trying deploy a crd using terrafom - kubernetes

I'm trying to deploy a crd using terraform with the code bellow
resource "kubectl_manifest" "crd-deploy" {
for_each = [ for crd in var.crdslist : crd ]
yaml_body = (fileexists("${path.module}/crds/${crd}.yaml") ? file("${path.module}/crds/${crd}") : "")
}
but i still get an error
Error: Invalid reference
on ../../00-modules/00-global/25-crd/10-crd.tf line 3, in resource "kubectl_manifest" "crd-deploy":
3: yaml_body = (fileexists("${path.module}/crds/${crd}.yaml") ? file("${path.module}/crds/${crd}") : "")
A reference to a resource type must be followed by at least one attribute access, specifying the resource name.
input.tf in module_level
variable "crdslist" {
type = list(string)
default = []
}
input.tf in execution_module_level
locals {
crdslist = ["crd-test"]
}
From where i run the terraform plan to deploy K8s CRDs
module "crds" {
source = "../../modules/global/25-crd"
crdslist = local.crdslist
}

Since the variable that is used is of type list(string), that means it cannot be used with for_each in its original form. The for_each meta-argument requires a variable to be of type map or set [1]:
If a resource or module block includes a for_each argument whose value is a map or a set of strings, Terraform will create one instance for each member of that map or set.
In order for the current configuration to work as expected, it is enough to use a Terraform built-in function toset [2]. The only change that needs to be made is inside of the module:
resource "kubectl_manifest" "crd-deploy" {
for_each = toset(var.crdslist)
yaml_body = (fileexists("${path.module}/crds/${each.vlaue}.yaml") ? file("${path.module}/crds/${each.vlaue}") : "")
}
Note that when the list is converted to a set, the key and value are the same, i.e., each.key and each.value can be used interchangeably [3].
[1] https://www.terraform.io/language/meta-arguments/for_each
[2] https://www.terraform.io/language/functions/toset
[3] https://www.terraform.io/language/meta-arguments/for_each#the-each-object

Related

terraform conditional creation of resource

I'm trying to create a unique terraform plan that can automate the installation of 2 separate environments in 2 Azure tenants.
Environment A consists of:
A container registry
A kubernetes cluster
Environment B consists of:
A kubernetes cluster that uses the registry in Environment A.
They will not share the same tfstate.
We don't want to have to maintain 2 terraform plans.
Idea: (We cannot test the solution at the moment).
we create a variable.tf where we define a variable that will contain the registry Id.
variable.tf:
variable DeployRegistry {
default = "True" or "False" (True for envA, False for envB)
}
variable registry_acr_id {
default = "/subscriptions/xxx/resourceGroups/yyy/providers/Microsoft.ContainerRegistry/registries/zzz"
}
registry.tf creates the registry and set output:
resource "azurerm_container_registry" "acr" {
count = "${var.DeployRegistry == "true" ? 1 : 0}"
etc...
}
output "registry_acr_id" {
value = "azurerm_container_registry.acr.id
sensitive = false
}
With condition DeployRegistry set to false, we can disable the creation of the registry and use registry_acr_id passed as input (eventually in tfvar).
Question: Will it work or will output "registry_acr_id" be empty when we are not creating it?
Because there is a condition to create or not the resource, but output is inconditionnally set.
You probably want something like this:
variable "registry" {
type = string
default = null
}
locals {
registry = var.registry != null ? var.registry : azurerm_container_registry.acr[0].id
}
resource "azurerm_container_registry" "acr" {
count = var.registry == null ? 1 : 0
# ...
}
output "registry" {
value = local.registry
}
The above code creates and outputs the new registry if the variable "registry" is not set. If the registry is set to something, it assumes there is no need to create new one.

Terraform: getting project id from azuredevops_project resource. Error data.azuredevops_projects.projectname.projects is set of object with 1 element

I am trying to access azuredevops project from terraform using azuredevops_devops resource. Using that project, I want to access repositories and create a new repo. But I am getting error in the second data block where I try to assign to project_id, but the output block prints the correct details.
data "azuredevops_projects" "sampleproject"{
name = "sample"
}
output "projectdetails"{
value = [for obj in data.azuredevops_projects.sampleproject.projects : obj.name]
}
error I receive here is: Incorrect attribute value type.data.azuredevops_projects.sampleproject.projects is set of object with 1 element :
data "azuredevops_git_repository" "samplerepo"{
project_id = [for obj in data.azuredevops_projects.sampleproject.projects : obj.name]
name = "Services-Template"
}
I am new to terraform, just practicing this for learning purpose.
Thanks for your answers, I tried everything but the below solution worked
initial outcome:
+ projectdetails = [
+ "74899dhjhjk-8909-4a97-9e9b-73488hfjikjd9",
]
outcoume after below solution:
element(data.azuredevops_projects.sampleproject.projects.*.project_id,0)
"74899dhjhjk-8909-4a97-9e9b-73488hfjikjd9"

In Terraform how do I join a string during the Terraform Plan Phase?

I am having trouble creating a resource. During the plan phase I get the error
Error: Incorrect attribute value type
var.ENVIRONMENT is a string, known only after apply
var.LOCATION_SHORTNAME is a string, known only after apply
var.PLATFORM is a string, known only after apply
Inappropriate value for attribute "identifier_uris": list of string │
required
Here is the resource I am working on:
resource "azuread_application" "yellow" {
display_name = join("", ["app-yellow-",var.PLATFORM,"-",var.ENVIRONMENT, "-",var.LOCATION_SHORTNAME])
identifier_uris = join("", ["https://app-yellow-",var.PLATFORM,"-",var.ENVIRONMENT, "-",var.LOCATION_SHORTNAME, ".azurewebsites.net"])
owners = [data.azuread_client_config.current.object_id]
web {
redirect_uris = join("", ["https://app-yellow-",var.PLATFORM,"-",var.ENVIRONMENT, "-",var.LOCATION_SHORTNAME, ".azurewebsites.net"])
}
}
It makes sense. Its looking for a string, but that string is not built yet because it does not run until the Apply Phase.
I have tried playing with depends_on, but have not had much luck. Any ideas?
Please try providing an array for uris as below:
resource "azuread_application" "yellow" {
display_name = join("", ["app-yellow-",var.PLATFORM,"-",var.ENVIRONMENT, "-",var.LOCATION_SHORTNAME])
identifier_uris = join("", ["https://app-yellow-",var.PLATFORM,"-",var.ENVIRONMENT, "-",var.LOCATION_SHORTNAME, ".azurewebsites.net"])
owners = [data.azuread_client_config.current.object_id]
web {
redirect_uris = [join("", ["https://app-yellow-",var.PLATFORM,"-",var.ENVIRONMENT, "-",var.LOCATION_SHORTNAME, ".azurewebsites.net"])]
}
}
I ended up making some changes. I created a variable called
variable "YELLOW_REDIRECT_URIS" {
type = string
}
And then added the var in Azure DevOps
TF_VAR_YELLOW_REDIRECT_URIS with the value I needed instead of trying to build it on the fly. It accomplishes the goal for now.

Problem attaching multiple NICs to a VM in Azure using a Terraform module

We are developing a module to create Linux based NVAs. Each NVA needs to have 3 NICs attached to it, each on a different subnet. At the moment, the module is able to successfully create the 3 NICs but the output from the NIC creation is of type tuple and we have not figured out how to use the NIC Resource IDs stored in the tuple as input into the VM creation.
We are trying to keep the module generic enough to be able to use it to create other types of Linux VMs that may not have multiple NICs.
Working code that calls module to create the NICs:
module "linux_vm" {
source = "../modules/test_module"
resource_group_name = azurerm_resource_group.saca_rg.name
nic_names = ["nic-mgmt","nic-int","nic-ext"]
subnet_id = ["${local.subnet_ids[4]}","${local.subnet_ids[3]}","${local.subnet_ids[2]}"]
ip_allocation = ["Static","Static","Static"]
static_ip = ["${cidrhost(local.static_ips[4], 4)}","${cidrhost(local.static_ips[3], 4)}","${cidrhost(local.static_ips[2], 4)}"]
}
Module code that creates the NICs:
resource "azurerm_network_interface" "vm_nic" {
count = length(var.nic_names)
name = var.nic_names[count.index]
resource_group_name = data.azurerm_resource_group.rg.name
location = data.azurerm_resource_group.rg.location
ip_configuration {
name = "${var.nic_names[count.index]}-ipconfig"
subnet_id = var.subnet_id[count.index]
private_ip_address_allocation = var.ip_allocation[count.index]
private_ip_address = var.static_ip[count.index]
}
}
The output from this is a tuple that resembles the output below:
"outputs": {
"nic_ids": {
"value": [
"/subscriptions/<sub_id>/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic-mgmt",
"/subscriptions/<sub_id>/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic-int",
"/subscriptions/<sub_id>/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic-ext"
],
"type": [
"tuple",
[
"string",
"string",
"string"
]
]
}
},
When we try to use this output as input into the VM creation block using the parameter below:
network_interface_ids = [azurerm_network_interface.vm_nic.*.id]
We get the error generated below:
Error: Incorrect attribute value type
on ../modules/test_module/main.tf line 27, in resource "azurerm_linux_virtual_machine" "vm":
27: network_interface_ids = [azurerm_network_interface.vm_nic.*.id]
|----------------
| azurerm_network_interface.vm_nic is tuple with 3 elements
Inappropriate value for attribute "network_interface_ids": element 0: string
required.
Discovered what the problem was.
We were trying to use the line below:
network_interface_ids = [azurerm_network_interface.vm_nic.*.id]
But since a tuple is like a list...this was basically nesting a list inside of a list. When we switched to the below, all 3 NICs assigned as expected:
network_interface_ids = azurerm_network_interface.vm_nic.*.id

Pre-deploying Kubernetes loadbalancer with terraform on DigitalOcean?

I'm learning about creating a k8s cluster on DO using terraform, I've been trying to take the ID of the single K8s node I've created, and reference it from the loadbalancer.
The main reasoning for this is so that I can declare the FQDN in the .tf file.
First, here is the cluster declaration:
variable "digitalocean_token" {}
provider "digitalocean" {
token = "${var.digitalocean_token}"
}
resource "digitalocean_kubernetes_cluster" "foo" {
name = "foo"
region = "nyc1"
version = "1.12.1-do.2"
node_pool {
name = "woker-pool"
size = "s-1vcpu-2gb"
node_count = 1
}
}
And here is the load balancer declaration:
resource "digitalocean_loadbalancer" "foo" {
name = "k8s-lb.nyc1"
region = "nyc1"
forwarding_rule {
entry_port = 80
entry_protocol = "http"
target_port = 80
target_protocol = "http"
}
droplet_ids = ["${digitalocean_kubernetes_cluster.foo.node_pool.0.id}"]
}
output "loadbalancer_ip" {
value = "${digitalocean_loadbalancer.foo.ip}"
}
resource "digitalocean_record" "terraform" {
domain = "example.com" # "${digitalocean_domain.example.name}"
type = "A"
name = "terraform"
value = "${digitalocean_loadbalancer.foo.ip}"
}
# Output the FQDN for the record
output "fqdn" {
value = "${digitalocean_record.terraform.fqdn}"
}
I'm guessing that maybe the digitalocean_loadbalancer resources is only setup to work with individual droplets?
Here are the output errors: when I run terraform apply:
* output.loadbalancer_ip: Resource 'digitalocean_loadbalancer.foo' not found for variable 'digitalocean_loadbalancer.foo.ip'
* digitalocean_record.terraform: Resource 'digitalocean_loadbalancer.foo' not found for variable 'digitalocean_loadbalancer.foo.ip'
* digitalocean_loadbalancer.foo: droplet_ids.0: cannot parse '' as int: strconv.ParseInt: parsing "d4292e64-9c0a-4afb-83fc-83f239bcb4af": invalid syntax
Pt. 2
I added a digitalocean_droplet resource, to see what kind of id was passed to the load balancer.
resource "digitalocean_droplet" "web" {
name = "web-1"
size = "s-1vcpu-1gb"
image = "ubuntu-18-04-x64"
region = "nyc1"
}
digitalocean_kubernetes_cluster.foo.node_pool.0.id = '6ae6a787-d837-4e78-a915-cb52155f66fe'
digitalocean_droplet.web.id = 132533158
So, the digitalocean_loadbalancer resource has an optional droplet_tag argument, which can be used to supply a common tag given to the created nodes/droplets.
However, when declaring a load-balancer inside kubernetes, a new one will still be created. So for now at least, it would appear that defining the domain/CNAME record with terraform isn't possible on digitalocean
You're using the wrong attribute reference for your load balancer droplet ids.
droplet_ids = ["${digitalocean_kubernetes_cluster.foo.node_pool.0.id}"]
This will use the node_pool id linked here
What you actually need to do is use the node_pool nodes id, which is referenced here
droplet_ids = "${digitalocean_kubernetes_cluster.foo.node_pool.0.nodes}"
The next problem you're going to have is that this returns a list of maps, and you'll need to build a list of ids from that. I'm not currently sure how to solve that, I'm afraid, but this should move you along hopefully.
It seems from your answer however, that what you want to do is update DNS for your loadbalancer.
You can do this external-dns using the digitalocean provider
Simply deploy this as a pod, specifying the required configuration, and ensure that the arg --source=service is set.
If you want to go a step further, and allow updating DNS with specific hostname, deploy an ingress controller like nginx-ingress and specify ingresses for your applications. The external-dns deployment (if you set --source=ingress) will the hostname from your ingress and update DNS for you.