Is there a way to modularize parameter preparation with Bicep? - azure-bicep

We have a bunch of naming- and ip-rules with a lot of resource groups to be created for dev, test and production. Because of the rules, we can calculate the names and ip ranges from two parameters: the stage to deploy to and the name of the service implemented by the resource group.
Example:
a service named "calculator" that includes a storage account and a vnet for stage "test" in a resource group
using
azure naming conventions
"xyz" as the abbreviation for our company
service name
additional specifier
10.1.0.0/16 for TEST
10.x.2.0/24 for "calculator"
we get:
resource group "rg-calculator-test"
storage account name = saxyzcalculatortest001
vnet = vnet-xyz-calculator-test 10.1.2.0/24
sub net 1 name = snet-xyz-calculator-test 10.1.2.0/26
sub net 2 name = snet-xyz-calculator-test-pep 10.1.2.128/28
To enforce consistency, I would like to share the calculation of all such names and addresses between all resource group deployment scripts.
Is there a way similar to "class files" or "includes" that I can use to include such a variable calculation into multiple .bicep files, so that I only need to pass a minimum set of variables (service name + stage) and the rest can be calculated in a consistent way without massive copy and paste of all those "var ... = ((stage == 'dev') ? '...' : (stage == 'test') ? '...' : '...')"?
I already did serch the documentation (maybe I simply did not recognize the Bicep-"thing" that can modularize the scripts in a way I need). What I was expecting was some synthak like
method CalculateDefaults 'modules/calculateDefaults.bicep' = {
name: 'CalculateDefaults'
params: {
stage: stage
serviceName: 'Calculator'
}
}
which produces outputs that I can use like
resource virtualNetwork 'Microsoft.Network/virtualNetworks#2021-02-01' = {
name: virtual_network_name
location: location
tags: tags
properties: {
addressSpace: {
addressPrefixes: [
CalculateDefaults.outputs.vnet_address_prefix
]
}

Related

Is it possible to fetch the image tag from a deployment in EKS using terraform kubernetes provider?

Context:
I'm reusing terraform modules and I deploy microservices using helm provider within terraform.
Problem:
I'm trying to translate this line into terraform code, to get the current image tag live from prod (in the interest of reusing it). I'm already using kubernetes provider's auth and doesn't make sense to pull kubectl in my CI just for this.
k get deploy my-deployment -n staging -o jsonpath='{$.spec.template.spec.containers[:1].image}'
Kubernetes terraform provider doesn't seem to support data blocks nor helm provider outputs blocks.
Does anyone know how could we get (read) the image tag of a deployment using terraform?
EDIT:
My deployment looks like this:
resource "helm_release" "example" {
name = "my-redis-release"
repository = "https://charts.bitnami.com/bitnami"
chart = "redis"
version = "6.0.1"
values = [
"${file("values.yaml")}"
]
set {
name = "image.tag"
value = "latest"
}
}
The tag will be a hash that will change often and passed on from another repo.
latest in this case should be replaced by the current running tag in the cluster. I can get it using kubectl, using the line above, but not sure how using terraform.
It turns out there are multiple ways of doing it, where the easiest one for me is to reference the set argument of the helm_release resource:
output "helm_image_tag" {
value = [ for setting in helm_release.example.set : setting.value if setting.name == "image.tag" ]
}
The output will then be a list where you can reference it in a shell script (or another scripting language):
+ helm_image_tag = [
+ "latest",
]
If the list format does not suit you, you can create a map output:
output "helm_image_tag" {
value = { for setting in helm_release.example.set : setting.name => setting.value if setting.name == "image.tag" }
}
This produces the following output:
+ helm_image_tag = {
+ "image.tag" = "latest"
}
By using terraform output helm_image_tag you can access this output value and decide what to do with it in the CI.

Unexpected error while passing variable group variables (Azure DevOps) to YAML pipeline

I'm a newbie to both Azure DevOps and Terraform but, I'm trying to deploy a pipeline using a YAML file.
I have tried to run a terraform plan using a YAML file and passing variables (from AZ DevOps) but, I got the following error:
2021-11-24T18:39:46.4604561Z Error: "name" may only contain alphanumeric characters, dash, underscores, parentheses and periods
2021-11-24T18:39:46.4604832Z
2021-11-24T18:39:46.4605940Z on modules/aks/main.tf line 2, in resource "azurerm_resource_group" "aks-resource-group":
2021-11-24T18:39:46.4606436Z 2: name = var.resource_group_name
2021-11-24T18:39:46.4606609Z
2021-11-24T18:39:46.4606722Z
2021-11-24T18:39:46.4606818Z
2021-11-24T18:39:46.4607525Z Error: Error: Subnet: (Name "#{vnet_subnet_name}#" / Virtual Network Name "#{vnet_name}#" / Resource Group "RG-XX-XXXX-XXXXX-001") was not found
2021-11-24T18:39:46.4608006Z
2021-11-24T18:39:46.4608580Z on modules/aks/main.tf line 16, in data "azurerm_subnet" "subnet-project":
2021-11-24T18:39:46.4609335Z 16: data "azurerm_subnet" "subnet-project" {
The 'name' has the following format at the Variable group in the Azure DevOps UI:
RG-XX-XXXX-XXXXX-001
This is the snippet of where I included the replace token at the YAML file:
displayName: 'Replace Secrets'
inputs:
targetFiles: |
variables.tfvars
encoding: 'utf-8'
actionOnMissing: fail
tokenPattern: #{MyVar}#
And this is a sample of the variables I have in a variable group:
variable-group-sample
Also, I replace the terraform.tfvars file with something like this:
resource_group_name = "#{resource_group_name}#"
I have checked the name inserted at the UI several times but I feel the error is pointing to something else I cannot see.
Have anyone experienced something related to this error?
Thank you in advance!
tokenPattern: #{MyVar}#
It is looking for the pattern #{MyVar}# to replace. Not "something contained between #{ and }#, but the actual value #{MyVar}#. I'm guessing it's expecting a regular expression, but I'm not familiar with that task.
So the end result is that your #{token values}# aren't getting replaced.
Assuming you're using https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens, you probably want to specify tokenPrefix: #{ and tokenSuffix: }# instead of using tokenPattern.
Now, having said that...
There is no reason for you to be using token replacement on a tfvars file. You should create different tfvars files for each environment, then pass in a tfvars file via the -var-file argument to Terraform. Secrets can be passed in on the command line via -var 'foo=bar'
Storing variables that represent application or deployment configuration in Azure DevOps (or GitHub, or any other CI system) is a big, big anti-pattern, because it's tightly coupling your deployment process to a particular platform. If you're sourcing all of your variables from Azure DevOps, you can't easily test locally or migrate to a different CI/CD provider like GitHub Actions in the future.
For values that shouldn't be in source control, such a secrets, you should use a secret provider like Azure KeyVault and integrate it with your application (or, in this case, use a data resource in Terraform to pull the necessary secrets automatically at deployment time).

Trigger multiple azure devops Pipelines in Parallel to create VM's on Azure using same Terraform module

I have terraform Module for example to create a VM on Azure and it works when I trigger the Pipeline.
But When I trigger the Pipeline twice it fails to create two VM's. How do I manipulate terraform State file ? Only way I can think of is two run multiple pipeline in different agents, does that work ?
What we have done is create terraform "common" modules (basically a subdirectory with tf files), which we source into a terraform environment multiple times with different parameters.
These we usually put into a list with a loop.
In your environments terraform:
locals {
azure_vms = [
{ name = "vm1", size = "Standard_B2s" },
{ name = "vm2", size = "Standard_B4s" }
]
}
module "my_azure_vm" {
source = "./common/my_azure_vm"
for_each = { for vm in local.azure_vms : vm.name => vm }
size = each.value.size
name = each.value.name
}
In common my_azure_vm, you can define inputs for size and name, then use those to create the VM's with your standard parameters.

Kubernetes secret with Flux and Terraform

I am new to terraform and devops in general. First I need to get ssh key from url to known host to later use for Flux.
data "helm_repository" "fluxcd" {
name = "fluxcd"
url = "https://charts.fluxcd.io"
}
resource "helm_release" "flux" {
name = "flux"
namespace = "flux"
repository = data.helm_repository.fluxcd.metadata[0].name
chart = "flux"
set {
name = "git.url"
value = "git.project"
}
set {
name = "git.secretName"
value = "flux-git-deploy"
}
set {
name = "syncGarbageCollection.enabled"
value = true
}
set_string {
name = "ssh.known_hosts"
value = Need this value from url
}
}
Then I need to generate key and use it to create kubernetes secret to communicate with gitlab repository.
resource "kubernetes_secret" "flux-git-deploy" {
metadata {
name = "flux-git-deploy"
namespace = "flux"
}
type = "Opaque"
data = {
identity = tls_private_key.flux.private_key_pem
}
}
resource "gitlab_deploy_key" "flux_deploy_key" {
title = "Title"
project = "ProjectID"
key = tls_private_key.flux.public_key_openssh
can_push = true
}
I am not sure if I am on the right track. Any advice will help.
There are few approaches you could use. These can be divided into "two categories":
generate manually the ssh_known_hosts and use the output through variables or files
create the file on the machine where you're running terraform with the command ssh-keyscan <git_domain> and set the path as value for ssh.known_hosts.
You can also use the file function directly in the variable or use the file output directly as env variable. Personally I would not recommend it because the value is saved directly in the terraform state but in this case it is not a critical issue. Critical would be if you're using ssh_keys or credentials.
Another approach would be to use the local-exec provisioner with a null_resource before you create the helm resource for flux and create the file directly in terraform. But additional to that you have to take care of accessing the file you created and also managing the triggers to run the command if a setting is changed.
In general, I would not use terraform for such things. It is fine to provide infrastructure like aws resources or services which are directly bound to the infrastructure but in order to create and run services you need a provisioning tool like ansible where you can run commands like "ssh-keyscan" directly as module. At the end you need a stable pipeline where you run ansible (or your favorite provisioning tool) after a terraform change.
But if you want to use only terraform you're going to right way.

List of AWS instances by TAG value using powershell

We are taging our AWS instances, I will like to retrieve a list of ALL our instances (ELB, S3, EC2, Security Groups) by TAG reference. for instance we consistently TAG our resources with something like this:
{ "Key": "Project",
"Value": "bananas"
},
How can we obtain trough power-shell a list of ALL our resources that contain the TAG Project value "bananas"?
I was able to get all my EC2s using the below script:
$instance = Get-EC2Instance
-Filter #( #{name='tag:Project'; values="bananas"};
#{name='instance-state-code'; values = 16} )
| Select-Object -ExpandProperty instances #Get instance ID ignoring any terminated instances
$instance | Export-CSV "C:\ec2.csv"
But I'm not sure how to obtain all my tagged resources using one script.
Check out the AWS Resource Groups Tagging API cmdlets -- these are relatively new, so you may have to update your AWS Tools for PowerShell to the latest version to be able to use them.
Example
The example below calls Get-RGTResource for the tag Key=Project, Value=Bananas, and filters the response to all ResourceARNs that were retrieved. The ResourceARN is a unique identifier for each AWS resource, and you can use these as a starting point to call out to other AWS services to get more details about each associated resource.
(Get-RGTResource -TagFilter #{Key="Project"; Values = #("bananas")}).ResourceARN
Example Output
arn:aws:ec2:us-east-1:<accountid>:instance/i-abcd1234
arn:aws:ec2:us-west-2:<accountid>:vpc/vpc-abcd1234
arn:aws:ec2:us-east-2:<accountid>:security-group/sg-abcd1234
arn:aws:elasticloadbalancing:us-east-1:<accountid>:loadbalancer/abcd1234
arn:aws:elasticmapreduce:us-east-1:<accountid>:cluster/abcd1234
Further Reading
AWS Documentation - Get-RGTResource
AWS Documentation - Amazon Resource Names (ARNs)