Terraform Azure run bash script on VM - azure

I'm trying to run a bash script on an Azure VM after deploying it with Terraform. I've tried different approaches but none of them have worked. With "custom_data", I assumed that the file will be uploaded and executed, however I'm not even seeing the file inside the VM.
I've also looked at "azurerm_virtual_machine_extension", but this does not give me the option to upload the file, only to execute commands or download from remote location (can't use fileUris due to requirements):
resource "azurerm_virtual_machine_extension" "test" {
name = "hostname"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
virtual_machine_name = "${azurerm_virtual_machine.test.name}"
publisher = "Microsoft.Azure.Extensions"
type = "CustomScript"
type_handler_version = "2.0"
settings = <<SETTINGS
{
"commandToExecute": "sh my_script.sh"
}
SETTINGS
tags = {
environment = "Production"
}
}
resource "azurerm_virtual_machine" "middleware_vm" {
name = "${var.middleware_vm}"
location = "${var.location}"
resource_group_name = "${azurerm_resource_group.middleware.name}"
network_interface_ids = ["${azurerm_network_interface.middleware.id}"]
vm_size = "Standard_DS4_v2"
storage_os_disk {
name = "centos_os_disk"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Premium_LRS"
}
storage_data_disk {
name = "managed_backup_disk"
create_option = "Empty"
caching = "ReadWrite"
disk_size_gb = "256"
managed_disk_type = "Premium_LRS"
lun = 0
}
storage_image_reference {
publisher = "OpenLogic"
offer = "CentOS"
sku = "7.5"
version = "latest"
}
os_profile {
computer_name = "${var.middleware_vm}"
admin_username = "middlewareadmin"
custom_data = "${file("scripts/middleware_disk.sh")}"
}

In azurerm_virtual_machine_extension, you can use:
protected_settings = <<PROTECTED_SETTINGS
{
"script": "${base64encode(file(var.scfile))}"
}
PROTECTED_SETTINGS
Please refer to my answer

First, the VM extension will just execute the script and do not copy the file to the VM. If you want to copy the script into the VM and then execute it. I will suggest you the Terraform provisioner file and remote-exec.
Here is the example that copies the file into the existing VM and executes the script:
resource "null_resource" "example" {
connection {
type = "ssh"
user = "azureuser"
password = "azureuser#2018"
host = "13.92.255.50"
port = 22
}
provisioner "file" {
source = "script.sh"
destination = "/tmp/script.sh"
}
provisioner "remote-exec" {
inline = [
"/bin/bash /tmp/script.sh"
]
}
}
Note: the script should be created in the current directory.

Related

Using Terraform, how do I create a VM on Azure that uses an existing managed disk?

I have imported a managed disk from a blob with terraform. Now I just need to create a VM with it (it's an OS disk). How?
I have:
resource "azurerm_managed_disk" "MyDisk" {
name = "MyDisk"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
storage_account_type = "Standard_LRS"
create_option = "Import"
storage_account_id = azurerm_storage_account.temp_storage.id
source_uri = "${azurerm_storage_container.images.id}/MyDisk.vhd"
disk_size_gb = "32"
tags = {
environment = "staging"
}
}
azurerm_linux_virtual_machine doesn't seem to have any way to take this managed disk and make a VM with it. Anyone know how?
thank you much
You can use azurerm_virtual_machine_data_disk_attachment. Example:
resource "azurerm_virtual_machine_data_disk_attachment" "example" {
managed_disk_id = azurerm_managed_disk.MyDisk.id
virtual_machine_id = azurerm_virtual_machine.MyMachine.id
lun = "10"
caching = "ReadWrite"
}
# <https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine>
resource "azurerm_virtual_machine" "main" {
name = "VoIP-VM"
location = var.location
resource_group_name = azurerm_resource_group.VoIP.name
network_interface_ids = [azurerm_network_interface.VoIP.id]
vm_size = "Standard_F2"
# Uncomment this line to delete the OS disk automatically when deleting the VM
# delete_os_disk_on_termination = true
# Uncomment this line to delete the data disks automatically when deleting the VM
# delete_data_disks_on_termination = true
storage_os_disk {
name = "${azurerm_managed_disk.MyDisk.name}"
caching = "ReadWrite"
create_option = "Attach"
managed_disk_type = "Standard_LRS"
managed_disk_id = "${azurerm_managed_disk.MyDisk.id}"
os_type = "linux"
}
tags = {
environment = "staging"
}
}

azure terraform attaching azure file share to windows machine

Problem statement
I am in the process to create an Azure VM cluster of windows os. till now I can create an Azure file share. and azure windows cluster. I want to attach file share created to each VM in my cluster. unable to find reference how to add same on windows VM.
code for this
resource "azurerm_storage_account" "main" {
name = "stor${var.environment}${var.cost_centre}${var.project}"
location = "${azurerm_resource_group.main.location}"
resource_group_name = "${azurerm_resource_group.main.name}"
account_tier = "${var.storage_account_tier}"
account_replication_type = "${var.storage_replication_type}"
}
resource "azurerm_storage_share" "main" {
name = "storageshare${var.environment}${var.cost_centre}${var.project}"
resource_group_name = "${azurerm_resource_group.main.name}"
storage_account_name = "${azurerm_storage_account.main.name}"
quota = "${var.storage_share_quota}"
}
resource "azurerm_virtual_machine" "vm" {
name = "vm-${var.location_id}-${var.environment}-${var.cost_centre}-${var.project}-${var.seq_id}-${count.index}"
location = "${azurerm_resource_group.main.location}"
resource_group_name = "${azurerm_resource_group.main.name}"
availability_set_id = "${azurerm_availability_set.main.id}"
vm_size = "${var.vm_size}"
network_interface_ids = ["${element(azurerm_network_interface.main.*.id, count.index)}"]
count = "${var.vm_count}"
storage_image_reference {
publisher = "${var.image_publisher}"
offer = "${var.image_offer}"
sku = "${var.image_sku}"
version = "${var.image_version}"
}
storage_os_disk {
name = "osdisk${count.index}"
create_option = "FromImage"
}
os_profile {
computer_name = "${var.vm_name}-${count.index}"
admin_username = "${var.admin_username}"
admin_password = "${var.admin_password}"
}
os_profile_windows_config {}
depends_on = ["azurerm_network_interface.main"]
}
Azure doesnt offer anything like that, so uou cannot do that natively, you need to create a script and run that script on the vm use script extension\dsc extension or if terraform supports that - with terraform.
https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/custom-script-linux

Forcing azurerm extension to wait until vm is deployed

I am running into an issue that is preventing my use of Terraform at the moment and wanted to see if anyone has seen the same behavior. I am using count to deploy multiple VM's along with a dsc extension for each VM.
Because I need the dsc extension to run on the first machine before running on the second machine, I attempted to use the depends_on property for the extension but due to the way I an using interpolation for machine naming, it fails due interpolation not being supported in depends_on.
Does anyone know a way around this? I have tested also tested pushing the machine names into a data resource but once again, I need the depends_on property to support interpolation.
resource "azurerm_virtual_machine" "Server" {
name = "${format("${var.customerProject}${var.environment}${var.machineAcronyms["Server"]}%02d", count.index + 1)}"
location = "${var.location}"
resource_group_name = "${azurerm_resource_group.rg.name}"
network_interface_ids = ["${element(azurerm_network_interface.Server_NIC.*.id, count.index)}"]
vm_size = "${var.Server_Specs["ServerType"]}"
count = "${var.Server_Specs["Number_of_Machines"]}"
storage_image_reference {
publisher = "${var.Server_Specs["Image_Publisher"]}"
offer = "${var.Server_Specs["Image_Offer"]}"
sku = "${var.Server_Specs["Image_sku"]}"
version = "${var.Server_Specs["Image_Version"]}"
}
plan {
name = "${var.Server_Specs["Plan_Name"]}"
publisher = "${var.Server_Specs["Plan_Publisher"]}"
product = "${var.Server_Specs["Plan_Product"]}"
}
os_profile {
computer_name = "${format("${var.customerProject}${var.environment}${var.machineAcronyms["Server"]}%02d", count.index + 1)}"
admin_username = "${var.AdminCredentials["Username"]}"
admin_password = "${var.AdminCredentials["Password"]}"
}
os_profile_windows_config {
provision_vm_agent = "true"
}
}
resource "azurerm_virtual_machine_extension" "Server_DSC" {
name = "${format("${var.customerProject}${var.environment}${var.machineAcronyms["Server"]}%02d", count.index + 1)}-dsc"
location = "${var.location}"
resource_group_name = "${azurerm_resource_group.rg.name}"
virtual_machine_name = "${format("${var.customerProject}${var.environment}${var.machineAcronyms["Server"]}%02d", count.index + 1)}"
publisher = "Microsoft.Powershell"
type = "DSC"
type_handler_version = "${var.dsc_extension}"
auto_upgrade_minor_version = true
depends_on = ["azurerm_storage_share.fileShare"]
count = "${var.Server_Specs["Number_of_Machines"]}"
settings = <<SETTINGS
{
"configuration": {
"url": "${var.resourceStore["fileShareUrl"]}${var.resourceStore["dscArchiveName"]}${var.azureCredentials["storageKey"]}",
"function": "contenthostingha",
"script": "contenthostingha.ps1"
},
"configurationArguments": {
"ExternalDNS": "${var.externalDNS}",
"NumberOfMachines": "${var.Server_Specs["Number_of_Machines"]}",
"AzureFileUrl": "azurerm_storage_share.fileShare.url",
"AzureFileShareKey": "${azurerm_storage_account.storageAccount.secondary_access_key}"
}
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"configurationArguments": {}
}
PROTECTED_SETTINGS
}
I haven't tried, but you might duplicate the Server_DSC resource (e.g. Server_DSC_0 and Server_DSC_nth), the _0 will use a fixed "0" instead of count and the number of instances should be lessen by 1, e.g using a local variable that subtract one to the original variable.

How can I use Terraform's file provisioner to copy from my local machine onto a VM?

I'm new to Terraform and have so far managed to get a basic VM (plus Resource Manager trimmings) up and running on Azure. The next task I have in mind is to have Terraform copy a file from my local machine into the newly created instance. Ideally I'm after a solution where the file will be copied each time the apply command is run.
I feel like I'm pretty close but so far I just get endless "Still creating..." statements once I apply (the file is 0kb so after a couple of mins it feels safe to give up).
So far, this is what I've got (based on this code): https://stackoverflow.com/a/37866044/4941009
Network
resource "azurerm_public_ip" "pub-ip" {
name = "PublicIp"
location = "${var.location}"
resource_group_name = "${azurerm_resource_group.rg.name}"
public_ip_address_allocation = "Dynamic"
domain_name_label = "${var.hostname}"
}
VM
resource "azurerm_virtual_machine" "vm" {
name = "${var.hostname}"
location = "${var.location}"
resource_group_name = "${azurerm_resource_group.rg.name}"
vm_size = "${var.vm_size}"
network_interface_ids = ["${azurerm_network_interface.nic.id}"]
storage_image_reference {
publisher = "${var.image_publisher}"
offer = "${var.image_offer}"
sku = "${var.image_sku}"
version = "${var.image_version}"
}
storage_os_disk {
name = "${var.hostname}osdisk1"
vhd_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}${azurerm_storage_container.cont.name}/${var.hostname}osdisk.vhd"
os_type = "${var.os_type}"
caching = "ReadWrite"
create_option = "FromImage"
}
os_profile {
computer_name = "${var.hostname}"
admin_username = "${var.admin_username}"
admin_password = "${var.admin_password}"
}
os_profile_windows_config {
provision_vm_agent = true
}
boot_diagnostics {
enabled = true
storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}"
}
tags {
environment = "${var.environment}"
}
}
File Provisioner
resource "null_resource" "copy-test-file" {
connection {
type = "ssh"
host = "${azurerm_virtual_machine.vm.ip_address}"
user = "${var.admin_username}"
password = "${var.admin_password}"
}
provisioner "file" {
source = "test.txt"
destination = "/tmp/test.txt"
}
}
As an aside, if I pass incorrect login details to the provisioner (ie rerun this after the VM has already been created and supply a different password to the provisioner) the behaviour is the same. Can anyone suggest where I'm going wrong?
I eventually got this working. Honestly, I kind of forgot about this question so I can't remember what my exact issue was, the example below though seems to work on my instance:
resource "null_resource" remoteExecProvisionerWFolder {
provisioner "file" {
source = "test.txt"
destination = "/tmp/test.txt"
}
connection {
host = "${azurerm_virtual_machine.vm.ip_address}"
type = "ssh"
user = "${var.admin_username}"
password = "${var.admin_password}"
agent = "false"
}
}
So it looks like the only difference here is the addition of agent = "false". This would make some sense as there's only one SSH authentication agent for windows and it's probable I hadn't explicitly specified to use that agent before. However it could well be that I ultimately changed something elsewhere in the configuration. Sorry future people for not being much help on this one.
FYI, windows instances you can connect via type winrm
resource "null_resource" "provision_web" {
connection {
host = "${azurerm_virtual_machine.vm.ip_address}"
type = "winrm"
user = "alex"
password = "alexiscool1!"
}
provisioner "file" {
source = "path/to/folder"
destination = "C:/path/to/destination"
}
}

SSH connection to Azure VM with Terraform

I have successfully created a VM as part of a Resource Group on Azure using Terraform. Next step is to ssh in the new machine and run a few commands. For that, I have created a provisioner as part of the VM resource and set up an SSH connection:
resource "azurerm_virtual_machine" "helloterraformvm" {
name = "terraformvm"
location = "West US"
resource_group_name = "${azurerm_resource_group.helloterraform.name}"
network_interface_ids = ["${azurerm_network_interface.helloterraformnic.id}"]
vm_size = "Standard_A0"
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "14.04.2-LTS"
version = "latest"
}
os_profile {
computer_name = "hostname"
user = "some_user"
password = "some_password"
}
os_profile_linux_config {
disable_password_authentication = false
}
provisioner "remote-exec" {
inline = [
"sudo apt-get install docker.io -y"
]
connection {
type = "ssh"
user = "some_user"
password = "some_password"
}
}
}
If I run "terraform apply", it seems to get into an infinite loop trying to ssh unsuccessfully, repeating this log over and over:
azurerm_virtual_machine.helloterraformvm (remote-exec): Connecting to remote host via SSH...
azurerm_virtual_machine.helloterraformvm (remote-exec): Host:
azurerm_virtual_machine.helloterraformvm (remote-exec): User: testadmin
azurerm_virtual_machine.helloterraformvm (remote-exec): Password: true
azurerm_virtual_machine.helloterraformvm (remote-exec): Private key: false
azurerm_virtual_machine.helloterraformvm (remote-exec): SSH Agent: true
I'm sure I'm doing something wrong, but I don't know what it is :(
EDIT:
I have tried setting up this machine without the provisioner, and I can SSH to it no problems with the given username/passwd. However I need to look up the host name in the Azure portal because I don't know how to retrieve it from Terraform. It's suspicious that the "Host:" line in the log is empty, so I wonder if it has anything to do with that?
UPDATE:
I've tried with different things like indicating the host name in the connection with
host = "${azurerm_public_ip.helloterraformip.id}"
and
host = "${azurerm_public_ip.helloterraformips.ip_address}"
as indicated in the docs, but with no success.
I've also tried using ssh-keys instead of password, but same result - infinite loop of connection tries, with no clear error message as of why it's not connecting.
I have managed to make this work. I changed several things:
Gave name of host to connection.
Configured SSH keys properly - they need to be unencrypted.
Took the connection element out of the provisioner element.
Here's the full working Terraform file, replacing the data like SSH keys, etc.:
# Configure Azure provider
provider "azurerm" {
subscription_id = "${var.azure_subscription_id}"
client_id = "${var.azure_client_id}"
client_secret = "${var.azure_client_secret}"
tenant_id = "${var.azure_tenant_id}"
}
# create a resource group if it doesn't exist
resource "azurerm_resource_group" "rg" {
name = "sometestrg"
location = "ukwest"
}
# create virtual network
resource "azurerm_virtual_network" "vnet" {
name = "tfvnet"
address_space = ["10.0.0.0/16"]
location = "ukwest"
resource_group_name = "${azurerm_resource_group.rg.name}"
}
# create subnet
resource "azurerm_subnet" "subnet" {
name = "tfsub"
resource_group_name = "${azurerm_resource_group.rg.name}"
virtual_network_name = "${azurerm_virtual_network.vnet.name}"
address_prefix = "10.0.2.0/24"
#network_security_group_id = "${azurerm_network_security_group.nsg.id}"
}
# create public IPs
resource "azurerm_public_ip" "ip" {
name = "tfip"
location = "ukwest"
resource_group_name = "${azurerm_resource_group.rg.name}"
public_ip_address_allocation = "dynamic"
domain_name_label = "sometestdn"
tags {
environment = "staging"
}
}
# create network interface
resource "azurerm_network_interface" "ni" {
name = "tfni"
location = "ukwest"
resource_group_name = "${azurerm_resource_group.rg.name}"
ip_configuration {
name = "ipconfiguration"
subnet_id = "${azurerm_subnet.subnet.id}"
private_ip_address_allocation = "static"
private_ip_address = "10.0.2.5"
public_ip_address_id = "${azurerm_public_ip.ip.id}"
}
}
# create storage account
resource "azurerm_storage_account" "storage" {
name = "someteststorage"
resource_group_name = "${azurerm_resource_group.rg.name}"
location = "ukwest"
account_type = "Standard_LRS"
tags {
environment = "staging"
}
}
# create storage container
resource "azurerm_storage_container" "storagecont" {
name = "vhd"
resource_group_name = "${azurerm_resource_group.rg.name}"
storage_account_name = "${azurerm_storage_account.storage.name}"
container_access_type = "private"
depends_on = ["azurerm_storage_account.storage"]
}
# create virtual machine
resource "azurerm_virtual_machine" "vm" {
name = "sometestvm"
location = "ukwest"
resource_group_name = "${azurerm_resource_group.rg.name}"
network_interface_ids = ["${azurerm_network_interface.ni.id}"]
vm_size = "Standard_A0"
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
storage_os_disk {
name = "myosdisk"
vhd_uri = "${azurerm_storage_account.storage.primary_blob_endpoint}${azurerm_storage_container.storagecont.name}/myosdisk.vhd"
caching = "ReadWrite"
create_option = "FromImage"
}
os_profile {
computer_name = "testhost"
admin_username = "testuser"
admin_password = "Password123"
}
os_profile_linux_config {
disable_password_authentication = false
ssh_keys = [{
path = "/home/testuser/.ssh/authorized_keys"
key_data = "ssh-rsa xxx email#something.com"
}]
}
connection {
host = "sometestdn.ukwest.cloudapp.azure.com"
user = "testuser"
type = "ssh"
private_key = "${file("~/.ssh/id_rsa_unencrypted")}"
timeout = "1m"
agent = true
}
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install docker.io -y",
"git clone https://github.com/somepublicrepo.git",
"cd Docker-sample",
"sudo docker build -t mywebapp .",
"sudo docker run -d -p 5000:5000 mywebapp"
]
}
tags {
environment = "staging"
}
}
According to your description, Azure Custom Script Extension is an option for you.
The Custom Script Extension downloads and executes scripts on Azure
virtual machines. This extension is useful for post deployment
configuration, software installation, or any other configuration /
management task.
Remove provisioner "remote-exec" instead of below:
resource "azurerm_virtual_machine_extension" "helloterraformvm" {
name = "hostname"
location = "West US"
resource_group_name = "${azurerm_resource_group.helloterraformvm.name}"
virtual_machine_name = "${azurerm_virtual_machine.helloterraformvm.name}"
publisher = "Microsoft.OSTCExtensions"
type = "CustomScriptForLinux"
type_handler_version = "1.2"
settings = <<SETTINGS
{
"commandToExecute": "apt-get install docker.io -y"
}
SETTINGS
}
Note: Command is executed by root user, don't use sudo.
More information please refer to this link: azurerm_virtual_machine_extension.
For a list of possible extensions, you can use the Azure CLI command az vm extension image list -o table
Update: The above example only supports single command. If you need to multiple commands. Like install docker on your VM, you need
apt-get update
apt-get install docker.io -y
Save it as a file named script.sh and save it to Azure Storage account or GitHub(The file should be public). Modify terraform file like below:
settings = <<SETTINGS
{
"fileUris": ["https://gist.githubusercontent.com/Walter-Shui/dedb53f71da126a179544c91d267cdce/raw/bb3e4d90e3291530570eca6f4ff7981fdcab695c/script.sh"],
"commandToExecute": "sh script.sh"
}
SETTINGS

Resources