Azure VM Extensions, written in Go
Tags: programming, go, azure, tutorial
Published: 2023-09-18 09:00:09

*Please note I am Dyslexic, so if things don't make grammatical sense, please do not hold it against me. Be Kind*

**Visit My Site for more Posts like this one: https://theclouddude.co.uk/**

There is a project I was recently involved in where they wanted a VM in Azure running IIS. If you don't wish to do this with Go Lang, you can follow this Blog post by Facundo Gauna. He explains how to do this with PowerShell: https://gaunacode.com/install-iis-on-azure-vm-using-terraform

This was also a learning exercise to see how Go interacts with the Azure SDK; I've been learning and using Go for around a year, and the saying. "Go is easy to learn but hard to master." It could not be more true. So please, if you don't wish to go on this learning exercise and are thinking well, this can just be done with Powershell, then be my guest in following that link ;). Now that we have housekeeping out of the way let me explain what I did.

*I made the VM using Terraform as this blog post is not about Terraform, but Go I will leave this link here to make a VM using Terraform.*

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/windows\_virtual\_machine.html

Our Journey begins at the `NewVirtualMachineExtensionsClient` function from the package armcompute (links at the bottom of the post for more information): `NewVirtualMachineExtensionsClient(AzureSubscriptionID, cred, nil)` This function tells the Azure API that I am about to create a new Virtual Machine Extension function and here are my Subscription ID and Credentials. This function does return an error, the way I handled the error is with the following piece of code:

```plaintext
if err != nil {
		log.Fatalf("failed to create client: %v", err)
	}
```

This is **simple** error handling. The Program will terminate and tell me the whole error right from the SDK. (**One Tip:** When working with the Azure SDK, don't write custom error messages; the SDK will forward detailed error messages for you, making researching these errors much easier.)

Here's the whole function in its entirety:

```plaintext
client, err := armcompute.NewVirtualMachineExtensionsClient(AzureSubscriptionID, cred, nil)
	if err != nil {
		log.Fatalf("failed to create client: %v", err)
	}
```

The first major hurdle I found in this journey is the following function, `BeginCreateOrUpdate`, actually updating the VM extension. The docs give an example of how this function is used, but when you fill the fields in with pointers, you will run into all sorts of errors with the SDK talking to Microsoft Azure.

What I found was that actually, it was not the best example of working with an extensions script; in researching for a few hours, I came across this Microsoft Article here: https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-tutorial-deploy-vm-extensions#:~:text=Save%20As.-,Edit%20the%20template,-Add%20a%20virtual

I then edited my calls to the struct. `VirtualMachineExtensionsClient`, which I believe all these fields were in the example JSON file given in the MS document. Here is an example of that JSON:

```plaintext
{
  "type": "Microsoft.Compute/virtualMachines/extensions",
  "apiVersion": "2021-04-01",
  "name": "[format('{0}/{1}', variables('vmName'), 'InstallWebServer')]",
  "location": "[parameters('location')]",
  "dependsOn": [
    "[format('Microsoft.Compute/virtualMachines/{0}',variables('vmName'))]"
  ],
  "properties": {
    "publisher": "Microsoft.Compute",
    "type": "CustomScriptExtension",
    "typeHandlerVersion": "1.7",
    "autoUpgradeMinorVersion": true,
    "settings": {
      "fileUris": [
        "https://raw.githubusercontent.com/Azure/azure-docs-json-samples/master/tutorial-vm-extension/installWebServer.ps1"
      ],
      "commandToExecute": "powershell.exe -ExecutionPolicy Unrestricted -File installWebServer.ps1"
    }
  }
}
```

As you can see from my code, I am now writing the following to the struct `VirtualMachineExtensionProperties` through a pointer using the `armcompute` package:

```plaintext
Properties: &armcompute.VirtualMachineExtensionProperties{
			Type:                    to.Ptr("CustomScriptExtension"),
			AutoUpgradeMinorVersion: to.Ptr(true),
			ForceUpdateTag:          to.Ptr("False"),
			InstanceView: &armcompute.VirtualMachineExtensionInstanceView{
				Name: to.Ptr("InstallWebServer"),
				Type: to.Ptr("CustomScriptExtension"),
			},
			Publisher: to.Ptr("Microsoft.Compute"),
			Settings: map[string]interface{}{
				"commandToExecute": "powershell -ExecutionPolicy Unrestricted Install-WindowsFeature -Name Web-Server -IncludeAllSubFeature -IncludeManagementTools",
			},
			SuppressFailures:   to.Ptr(true),
			TypeHandlerVersion: to.Ptr("1.7"),
		},
```

What I have found to be quite hard when working with Azure SDK is that there are many nested structs within structs. The struct fields themselves are based on JSON files. I do believe these are essentially just ARM templates being called. It's actually very hard to work out in the first instance how to change a property of a struct in the Azure SDK.

For example, the current function we are working on requires us first to pass in: `armcompute.VirtualMachineExtension`, which in itself is a struct from the package `armcompute`. Then because the struct we actually want to edit is `VirtualMachineExtensionProperties`, you have to do some crazy pointer work to edit those fields like this:

```plaintext
Type:                    to.Ptr("CustomScriptExtension"),
			AutoUpgradeMinorVersion: to.Ptr(true),
			ForceUpdateTag:          to.Ptr("False"),
```

Miki Tebeka (Who has written the book Go Brain Teasers") showed me another way to do this, which I may try another time. For now, this works.

Essentially to make a VM Extension using Go you are left with the following Code. I hope I have explained various parts of this code well enough for you to understand what is happening.

```plaintext
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4"
)

func main() {
	AzureSubscriptionID := "VALUE"
	myVM := "vm-onpremvm"
	myVMLocation := "North Europe"
	resourceGroup := "RG-JC-Sandbox"
	myVMExtension := "IIS"
	// Authenticate To Azure

	azCLI, err := azidentity.NewAzureCLICredential(nil)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Authenticated!!")
	}
	cred := azCLI
	ctx := context.Background()
	// Edit the VM Extension
	client, err := armcompute.NewVirtualMachineExtensionsClient(AzureSubscriptionID, cred, nil)
	if err != nil {
		log.Fatalf("failed to create client: %v", err)
	}
	poller, err := client.BeginCreateOrUpdate(ctx, resourceGroup, myVM, myVMExtension, armcompute.VirtualMachineExtension{
		Location: to.Ptr(myVMLocation),
		Tags: map[string]*string{
			"IISExtension": to.Ptr("IISExtension"),
		},
		Properties: &armcompute.VirtualMachineExtensionProperties{
			Type:                    to.Ptr("CustomScriptExtension"),
			AutoUpgradeMinorVersion: to.Ptr(true),
			ForceUpdateTag:          to.Ptr("False"),
			InstanceView: &armcompute.VirtualMachineExtensionInstanceView{
				Name: to.Ptr("InstallWebServer"),
				Type: to.Ptr("CustomScriptExtension"),
			},
			Publisher: to.Ptr("Microsoft.Compute"),
			Settings: map[string]interface{}{
				"commandToExecute": "powershell -ExecutionPolicy Unrestricted Install-WindowsFeature -Name Web-Server -IncludeAllSubFeature -IncludeManagementTools",
			},
			SuppressFailures:   to.Ptr(true),
			TypeHandlerVersion: to.Ptr("1.7"),
		},
	},
		nil)
	if err != nil {
		log.Fatalf("failed to finish the request: %v", err)
	}
	res, err := poller.PollUntilDone(ctx, nil)
	if err != nil {
		log.Fatalf("failed to pull the result: %v", err)
	}
	fmt.Printf("%v", res)
}
```

I hope this has helped someone; this was my first ever blog post about Go; I hope you have enjoyed it. I am no Go expert. If you wish to get in touch with me I can be reached here at jason@theclouddude.co.uk Id like to say thank you for reading this far. :)

I would also like to thank the **Gophers** at the Gopher Slack found here: gophers.slack.com for helping me hugely with this project and pointing me in the right direction.

**Visit My Site for more Posts like this one: https://theclouddude.co.uk/**

The Resources I used were:

https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-tutorial-deploy-vm-extensions

https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4#VirtualMachineExtensionsClient

https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4#ClientFactory.NewVirtualMachineExtensionsClient:~:text=README,ClientFactory)%20NewVirtualMachineExtensionsClient%20%C2%B6