Add support for Virtual Machine Scale Sets in the Boundary Azure Plugin (#22)

* first pass at pulling in VMSS data

* infer vmss set id and external name from vmss data as none of these items are present by default

* updated scale set name formatting to align with azure portal and json outputs

* Update plugin/plugin.go

Updated following code review

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>

* Update plugin/plugin.go

Updated following code review

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>

* complete refactoring of initial implementation following code review, taking into accounts comments and common sense updates

* updated README.md to reflect changes to support VMSS

* fixed typo in README.md

* WIP: initial updates following code review

* refactored resource functions

* removed instrumentation statements

* resolve ifname comments

* address formatting issues in accordance with style recommendations

---------

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
This commit is contained in:
Benjamin HolmesandJohan Brandhorst-Satzkorn authored and GitHub committed 2025-01-22 10:36:03 -08:00
1 parent 84ee22dcfd
commit 30ea98c877
12 files changed
+1598 -311

No files matched your search

+288 -2
View File
@@ -7,6 +7,8 @@ import (
"context"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
@@ -448,7 +450,7 @@ func testGetHostStructs(t *testing.T) (*hostcatalogs.HostCatalog, []*hostsets.Ho
Id: "set1",
Attrs: &hostsets.HostSet_Attributes{
Attributes: wrapMap(t, map[string]interface{}{
constFilter: constDefaultFilter,
constFilter: "",
}),
},
},
@@ -456,7 +458,7 @@ func testGetHostStructs(t *testing.T) (*hostcatalogs.HostCatalog, []*hostsets.Ho
Id: "set2",
Attrs: &hostsets.HostSet_Attributes{
Attributes: wrapMap(t, map[string]interface{}{
constFilter: constDefaultFilter,
constFilter: "",
}),
},
},
@@ -505,3 +507,287 @@ func testCreateInitialSecret(t *testing.T, hostCatalog *hostcatalogs.HostCatalog
return authzInfo, initialCred, cleanup
}
func TestSplitId(t *testing.T) {
tests := []struct {
name string
input string
expectedService string
expectedResource string
wantRG string
wantName string
wantErr bool
}{
{
name: "valid resource id",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
expectedService: "Microsoft.Compute",
expectedResource: "virtualMachines",
wantRG: "rg1",
wantName: "vm1",
wantErr: false,
},
{
name: "valid with different case",
input: "/SUBSCRIPTIONS/sub1/RESOURCEGROUPS/rg1/PROVIDERS/MICROSOFT.COMPUTE/VIRTUALMACHINES/vm1",
expectedService: "Microsoft.Compute",
expectedResource: "virtualMachines",
wantRG: "rg1",
wantName: "vm1",
wantErr: false,
},
{
name: "invalid - trailing slash",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1/",
expectedService: "Microsoft.Compute",
expectedResource: "virtualMachines",
wantRG: "",
wantName: "",
wantErr: true,
},
{
name: "invalid - wrong service",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Storage/virtualMachines/vm1",
expectedService: "Microsoft.Compute",
expectedResource: "virtualMachines",
wantRG: "",
wantName: "",
wantErr: true,
},
{
name: "invalid - wrong resource type",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1",
expectedService: "Microsoft.Compute",
expectedResource: "virtualMachines",
wantRG: "",
wantName: "",
wantErr: true,
},
{
name: "invalid - missing segments",
input: "/subscriptions/sub1/resourceGroups/rg1",
expectedService: "Microsoft.Compute",
expectedResource: "virtualMachines",
wantRG: "",
wantName: "",
wantErr: true,
},
{
name: "invalid - empty string",
input: "",
expectedService: "Microsoft.Compute",
expectedResource: "virtualMachines",
wantRG: "",
wantName: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotRG, gotName, err := splitId(tt.input, tt.expectedService, tt.expectedResource)
if (err != nil) != tt.wantErr {
t.Errorf("splitId() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotRG != tt.wantRG {
t.Errorf("splitId() gotRG = %v, want %v", gotRG, tt.wantRG)
}
if gotName != tt.wantName {
t.Errorf("splitId() gotName = %v, want %v", gotName, tt.wantName)
}
})
}
}
func TestExtractResourceType(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "valid compute resource",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
want: "virtualMachines",
wantErr: false,
},
{
name: "valid compute resource different case",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/MICROSOFT.COMPUTE/virtualMachines/vm1",
want: "virtualMachines",
wantErr: false,
},
{
name: "valid with trailing slash",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1/",
want: "virtualMachines",
wantErr: false,
},
{
name: "different compute resource type",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1",
want: "diskEncryptionSets",
wantErr: false,
},
{
name: "non-compute resource",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Storage/storageAccounts/sa1",
want: "",
wantErr: false,
},
{
name: "malformed - missing provider",
input: "/subscriptions/sub1/resourceGroups/rg1/Microsoft.Compute/virtualMachines/vm1",
want: "",
wantErr: true,
},
{
name: "malformed - missing resource type",
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute",
want: "",
wantErr: true,
},
{
name: "empty string",
input: "",
want: "",
wantErr: true,
},
{
name: "too short",
input: "/subscriptions/sub1",
want: "",
wantErr: true,
},
{
name: "malformed structure",
input: "/something/else/entirely",
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := extractResourceType(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("extractResourceType() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("extractResourceType() = %v, want %v", got, tt.want)
}
})
}
}
func TestCreateHostFromResource(t *testing.T) {
// Setup common test data
testNetwork := networkInfo{
IpAddresses: []string{"192.168.1.1", "192.168.1.2"},
}
// Use full Azure resource paths as keys
vmssPath := "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1"
vmPath := "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-resource1"
resourceToSetMap := map[string][]string{
vmssPath: {"set1", "set2"},
vmPath: {"set3", "set4"},
}
tests := []struct {
name string
resourceID string
network networkInfo
setMap map[string][]string
want *pb.ListHostsResponseHost
wantErr bool
errMessage string
}{
{
name: "valid VMSS resource",
resourceID: vmssPath + "/virtualMachines/vm1",
network: testNetwork,
setMap: resourceToSetMap,
want: &pb.ListHostsResponseHost{
ExternalId: vmssPath + "/virtualMachines/vm1",
ExternalName: "vmss1_vm1",
IpAddresses: []string{"192.168.1.1", "192.168.1.2"},
SetIds: []string{"set1", "set2"},
},
wantErr: false,
},
{
name: "valid VM resource",
resourceID: vmPath,
network: testNetwork,
setMap: resourceToSetMap,
want: &pb.ListHostsResponseHost{
ExternalId: vmPath,
ExternalName: "vm-resource1",
IpAddresses: []string{"192.168.1.1", "192.168.1.2"},
SetIds: []string{"set3", "set4"},
},
wantErr: false,
},
{
name: "invalid resource ID",
resourceID: "invalid-resource-id",
network: testNetwork,
setMap: resourceToSetMap,
want: nil,
wantErr: true,
errMessage: "invalid resource ID",
},
{
name: "unsupported resource type",
resourceID: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/unsupportedResource/resource1",
network: testNetwork,
setMap: resourceToSetMap,
want: nil,
wantErr: true,
errMessage: "unsupported resource type",
},
{
name: "VMSS with valid resource type but invalid structure",
resourceID: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachineScaleSets", // Missing required segments after virtualMachineScaleSets
network: testNetwork,
setMap: resourceToSetMap,
want: nil,
wantErr: true,
errMessage: "could not determine set for resource ID: not a valid VMSS resource ID",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := createHostFromResource(tt.resourceID, tt.network, tt.setMap)
// Check error cases
if tt.wantErr {
if err == nil {
t.Errorf("createHostFromResource() expected error containing %q, got nil", tt.errMessage)
return
}
if !strings.Contains(err.Error(), tt.errMessage) {
t.Errorf("createHostFromResource() error = %v, want error containing %q", err, tt.errMessage)
}
return
}
// Check non-error cases
if err != nil {
t.Errorf("createHostFromResource() unexpected error: %v", err)
return
}
// Compare the results
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("createHostFromResource() = %v, want %v", got, tt.want)
}
})
}
}