DEV Community

Cover image for Azure VM Extensions, written in Go
Jason Content
Jason Content

Posted on

Azure VM Extensions, written in Go

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:

if err != nil {
        log.Fatalf("failed to create client: %v", err)
    }
Enter fullscreen mode Exit fullscreen mode

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:

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

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:

{
  "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"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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"),
        },
Enter fullscreen mode Exit fullscreen mode

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:

Type:                    to.Ptr("CustomScriptExtension"),
            AutoUpgradeMinorVersion: to.Ptr(true),
            ForceUpdateTag:          to.Ptr("False"),
Enter fullscreen mode Exit fullscreen mode

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (1)

Collapse
 
schemetastic profile image
Schemetastic (Rodrigo)

Hello Jason, welcome to the DEV Community.

Dyslexic? I wouldn't have noticed that, even though I'm not familiar with Azure and Go, I could tell this is a good post.

Since you are new here, let me give you a few tips:

  • You can highlight code adding the language name after the first 3 backticks in code blocks. e.g. ```go
if err != nil {
    log.Fatalf("failed to create client: %v", err)
}
Enter fullscreen mode Exit fullscreen mode
  • Adding practical examples (like things you could do with your solution) and extra imagery can make the post more dynamic, visually appealing and easier to understand. Usually I notice those are the posts that get more attention here.
  • Keep writing and get even more involved in the community, this will make you earn badges. As you keep growing and being kind to all, you can become a Trusted Member in the DEV Community that will lead you to extra benefits.

Why am I sharing this with you? I see you want to share good content, and I want to give you a little boost that can save you some time. When you get even better, send me the link to another post, please 😉