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.
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)
}
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)
}
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"
}
}
}
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"),
},
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"),
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)
}
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:
Top comments (1)
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:
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 😉