mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
Add ARM Network dependency (#69093)
* Add ARM Network dependency * Deduplicate scale set names * make go-mod-tidy-all * Add ListAll and address feedback * Limit resource group for scale sets * Add RG check * case-insensitive sub id check
This commit is contained in:
@@ -72,6 +72,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0=
|
||||
|
||||
@@ -58,6 +58,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0=
|
||||
|
||||
@@ -35,6 +35,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.3.0
|
||||
|
||||
@@ -91,6 +91,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0=
|
||||
|
||||
@@ -44,6 +44,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.3.0 // indirect
|
||||
|
||||
@@ -65,6 +65,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0=
|
||||
|
||||
@@ -47,6 +47,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.3.0 // indirect
|
||||
|
||||
@@ -91,6 +91,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0=
|
||||
|
||||
@@ -56,6 +56,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.3.0 // indirect
|
||||
|
||||
@@ -122,6 +122,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 h1:d
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0/go.mod h1:6z3b+JdBLH0eMzfBex/cvEIoEFVEwXuB0wbgdfN11iM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 h1:3jDMffAwnvs6qmOqhjNVHB29AKxs6brnzJeo65E1YwM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0/go.mod h1:0mKVz3WT8oNjBunT1zD/HPwMleQ72QClMa7Gmsm+6Kc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0 h1:DgqO2jYgDEqmN8W5sPP+ZU7Tfxyn+i9RqXtNsX6Enb8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7 v7.2.0/go.mod h1:FBChJszHNRdH5AYJ+Y/NgWilJihKa5WcSlFrNnj2eY0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0=
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3"
|
||||
@@ -763,3 +764,58 @@ func NewUserAssignedIdentity(subscription, resourceGroupName, resourceName, clie
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ARMNetworkMock mocks armnetwork.InterfacesClient.
|
||||
type ARMNetworkMock struct {
|
||||
// NetworkInterfaces maps resource group name to the standalone NICs in it.
|
||||
NetworkInterfaces map[string][]*armnetwork.Interface
|
||||
// VMSSNetworkInterfaces maps "<resourceGroup>/<scaleSetName>" to the NICs
|
||||
// of that uniform scale set.
|
||||
VMSSNetworkInterfaces map[string][]*armnetwork.Interface
|
||||
// VMSSListErrs maps scale set names to an error returned when listing
|
||||
// that scale set's NICs.
|
||||
VMSSListErrs map[string]error
|
||||
// NoAuth indicates that the mock should return an access denied error
|
||||
// for all requests.
|
||||
NoAuth bool
|
||||
}
|
||||
|
||||
// Fail if ARMNetworkMock does not implement networkInterfacesLister.
|
||||
var _ networkInterfacesLister = (*ARMNetworkMock)(nil)
|
||||
|
||||
func (m *ARMNetworkMock) NewListPager(resourceGroup string, _ *armnetwork.InterfacesClientListOptions) *runtime.Pager[armnetwork.InterfacesClientListResponse] {
|
||||
return newPagerHelper(m.NoAuth, func() (armnetwork.InterfacesClientListResponse, error) {
|
||||
return armnetwork.InterfacesClientListResponse{
|
||||
InterfaceListResult: armnetwork.InterfaceListResult{
|
||||
Value: m.NetworkInterfaces[resourceGroup],
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *ARMNetworkMock) NewListAllPager(_ *armnetwork.InterfacesClientListAllOptions) *runtime.Pager[armnetwork.InterfacesClientListAllResponse] {
|
||||
return newPagerHelper(m.NoAuth, func() (armnetwork.InterfacesClientListAllResponse, error) {
|
||||
var allNics []*armnetwork.Interface
|
||||
for _, nics := range m.NetworkInterfaces {
|
||||
allNics = append(allNics, nics...)
|
||||
}
|
||||
return armnetwork.InterfacesClientListAllResponse{
|
||||
InterfaceListResult: armnetwork.InterfaceListResult{
|
||||
Value: allNics,
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *ARMNetworkMock) NewListVirtualMachineScaleSetNetworkInterfacesPager(resourceGroup, scaleSetName string, _ *armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesOptions) *runtime.Pager[armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse] {
|
||||
return newPagerHelper(m.NoAuth, func() (armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse, error) {
|
||||
if err := m.VMSSListErrs[scaleSetName]; err != nil {
|
||||
return armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse{}, err
|
||||
}
|
||||
return armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse{
|
||||
InterfaceListResult: armnetwork.InterfaceListResult{
|
||||
Value: m.VMSSNetworkInterfaces[resourceGroup+"/"+scaleSetName],
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7"
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/utils/slices"
|
||||
)
|
||||
|
||||
// IPConfiguration represents an Azure network interface IP configuration.
|
||||
type IPConfiguration struct {
|
||||
// Primary indicates whether this IP configuration is the primary one for the network interface.
|
||||
Primary bool
|
||||
// PrivateIP is the private IP address assigned to this IP configuration. This
|
||||
// field may be in CIDR notation, if this isn't the Primary IP configuration.
|
||||
PrivateIP string
|
||||
}
|
||||
|
||||
// NetworkInterface represents an Azure network interface.
|
||||
type NetworkInterface struct {
|
||||
// ID is the resource ID of the network interface.
|
||||
ID string
|
||||
// Name is the resource name of the network interface.
|
||||
Name string
|
||||
// Primary indicates whether this network interface is the primary one for the
|
||||
// virtual machine or virtual machine scale set instance.
|
||||
Primary bool
|
||||
// AttachedVMID is the resource ID of the virtual machine the network
|
||||
// interface is attached to. Empty for network interfaces not attached to any
|
||||
// virtual machine. Resource IDs are not casing-stable across Azure APIs, so
|
||||
// compare case-insensitively when matching against VM resource IDs.
|
||||
AttachedVMID string
|
||||
// IPConfigurations is a list of IP configurations associated with the network
|
||||
// interface.
|
||||
IPConfigurations []IPConfiguration
|
||||
}
|
||||
|
||||
// networkInterfacesLister is satisfied by *armnetwork.InterfacesClient.
|
||||
type networkInterfacesLister interface {
|
||||
// NewListPager lists Azure network interfaces in the given resource group.
|
||||
NewListPager(resourceGroup string, opts *armnetwork.InterfacesClientListOptions) *runtime.Pager[armnetwork.InterfacesClientListResponse]
|
||||
// NewListAllPager lists all Azure network interfaces in the subscription.
|
||||
NewListAllPager(opts *armnetwork.InterfacesClientListAllOptions) *runtime.Pager[armnetwork.InterfacesClientListAllResponse]
|
||||
// NewListVirtualMachineScaleSetNetworkInterfacesPager lists Azure network interfaces in the given resource group and scale set.
|
||||
NewListVirtualMachineScaleSetNetworkInterfacesPager(resourceGroup, scaleSetName string, opts *armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesOptions) *runtime.Pager[armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse]
|
||||
}
|
||||
|
||||
// NetworkInterfacesClient is an interface for listing Azure network interfaces.
|
||||
type NetworkInterfacesClient interface {
|
||||
// ListNetworkInterfaces lists all network interfaces in the given resource group.
|
||||
ListNetworkInterfaces(ctx context.Context, resourceGroup string, scaleSetIDs ...string) ([]*NetworkInterface, error)
|
||||
}
|
||||
|
||||
type networkInterfacesClient struct {
|
||||
networkInterfacesLister networkInterfacesLister
|
||||
logger *slog.Logger
|
||||
// subscriptionID is the Azure subscription ID to list network interfaces from.
|
||||
// This is used to validate that any scale set IDs passed are in the same
|
||||
// subscription.
|
||||
subscriptionID string
|
||||
}
|
||||
|
||||
func NewNetworkInterfacesClient(subscriptionID string, cred azcore.TokenCredential, options *arm.ClientOptions) (NetworkInterfacesClient, error) {
|
||||
networkInterfacesClient, err := armnetwork.NewInterfacesClient(subscriptionID, cred, options)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "failed to create Azure network interfaces client")
|
||||
}
|
||||
|
||||
config := NetworkInterfacesClientConfig{
|
||||
NetworkInterfacesAPI: networkInterfacesClient,
|
||||
SubscriptionID: subscriptionID,
|
||||
}
|
||||
return NewNetworkInterfacesClientByAPI(config), nil
|
||||
}
|
||||
|
||||
// NetworkInterfacesClientConfig is a configuration struct for creating a
|
||||
// NetworkInterfacesClient.
|
||||
type NetworkInterfacesClientConfig struct {
|
||||
NetworkInterfacesAPI networkInterfacesLister
|
||||
Logger *slog.Logger
|
||||
// SubscriptionID is the Azure subscription ID to list network interfaces from.
|
||||
// This is used to validate that any scale set IDs passed are in the same
|
||||
// subscription.
|
||||
SubscriptionID string
|
||||
}
|
||||
|
||||
func NewNetworkInterfacesClientByAPI(config NetworkInterfacesClientConfig) NetworkInterfacesClient {
|
||||
if config.Logger == nil {
|
||||
config.Logger = slog.Default().With(teleport.ComponentKey, "azure_networkinterfaces_client")
|
||||
}
|
||||
|
||||
return &networkInterfacesClient{
|
||||
networkInterfacesLister: config.NetworkInterfacesAPI,
|
||||
logger: config.Logger,
|
||||
subscriptionID: config.SubscriptionID,
|
||||
}
|
||||
}
|
||||
|
||||
// ListNetworkInterfaces lists all network interfaces in the given resource group.
|
||||
//
|
||||
// scaleSetIDs is a list of resource IDs for the virtual machine scale sets.
|
||||
// e.g. "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Compute/virtualMachineScaleSets/<vmss>"
|
||||
// If a scale set ID is not in the same subscription or resource group, it is
|
||||
// skipped with a warning.
|
||||
func (c *networkInterfacesClient) ListNetworkInterfaces(ctx context.Context, resourceGroup string, scaleSetIDs ...string) ([]*NetworkInterface, error) {
|
||||
standardAndFlexibleNICs, err := c.listStandardAndFlexibleNICs(ctx, resourceGroup)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "failed to list standard and flexible VMSS NICs")
|
||||
}
|
||||
|
||||
// Currently, we're more concerned with listing standard and flexible VMSS
|
||||
// NICs, so if a uniform VMSS NIC listing fails, it is logged and the process
|
||||
// continues.
|
||||
uniformNICs := c.listUniformNICs(ctx, resourceGroup, scaleSetIDs)
|
||||
|
||||
return append(standardAndFlexibleNICs, uniformNICs...), nil
|
||||
}
|
||||
|
||||
func (c *networkInterfacesClient) listStandardAndFlexibleNICs(ctx context.Context, resourceGroup string) ([]*NetworkInterface, error) {
|
||||
var pager apiPager[armnetwork.Interface]
|
||||
if resourceGroup == types.Wildcard {
|
||||
pager = newAPIPager(
|
||||
c.networkInterfacesLister.NewListAllPager(nil),
|
||||
func(resp armnetwork.InterfacesClientListAllResponse) []*armnetwork.Interface {
|
||||
return resp.InterfaceListResult.Value
|
||||
},
|
||||
)
|
||||
} else {
|
||||
pager = newAPIPager(
|
||||
c.networkInterfacesLister.NewListPager(resourceGroup, nil),
|
||||
func(resp armnetwork.InterfacesClientListResponse) []*armnetwork.Interface {
|
||||
return resp.InterfaceListResult.Value
|
||||
},
|
||||
)
|
||||
}
|
||||
return c.collectNICs(ctx, pager)
|
||||
}
|
||||
|
||||
func (c *networkInterfacesClient) listUniformNICs(ctx context.Context, resourceGroup string, scaleSetIDs []string) []*NetworkInterface {
|
||||
var allNICs []*NetworkInterface
|
||||
for _, scaleSetID := range slices.DeduplicateKey(scaleSetIDs, strings.ToLower) {
|
||||
id, err := arm.ParseResourceID(scaleSetID)
|
||||
if err != nil {
|
||||
c.logger.WarnContext(ctx, "Failed to parse scale set ID", "scale_set_id", scaleSetID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check the resource ID is for a uniform VMSS in the same subscription and resource group.
|
||||
isComputeNamespace := strings.EqualFold(id.ResourceType.Namespace, "Microsoft.Compute")
|
||||
isVMSSResourceType := strings.EqualFold(id.ResourceType.Type, "virtualMachineScaleSets")
|
||||
if !isComputeNamespace || !isVMSSResourceType {
|
||||
c.logger.WarnContext(ctx, "Skipping non-uniform scale set ID", "scale_set_id", scaleSetID)
|
||||
continue
|
||||
}
|
||||
if !strings.EqualFold(id.SubscriptionID, c.subscriptionID) {
|
||||
c.logger.WarnContext(ctx, "Skipping scale set ID in a different subscription", "scale_set_id", scaleSetID, "subscription_id", id.SubscriptionID)
|
||||
continue
|
||||
}
|
||||
if resourceGroup != types.Wildcard && !strings.EqualFold(id.ResourceGroupName, resourceGroup) {
|
||||
c.logger.WarnContext(ctx, "Skipping scale set ID in a different resource group", "scale_set_id", scaleSetID, "resource_group", resourceGroup)
|
||||
continue
|
||||
}
|
||||
|
||||
pager := newAPIPager(
|
||||
c.networkInterfacesLister.NewListVirtualMachineScaleSetNetworkInterfacesPager(id.ResourceGroupName, id.Name, nil),
|
||||
func(resp armnetwork.InterfacesClientListVirtualMachineScaleSetNetworkInterfacesResponse) []*armnetwork.Interface {
|
||||
return resp.InterfaceListResult.Value
|
||||
},
|
||||
)
|
||||
nics, err := c.collectNICs(ctx, pager)
|
||||
if err != nil {
|
||||
c.logger.WarnContext(ctx, "Failed to list NICs from Uniform VMSS", "scale_set_id", scaleSetID, "error", err)
|
||||
continue
|
||||
}
|
||||
allNICs = append(allNICs, nics...)
|
||||
}
|
||||
return allNICs
|
||||
}
|
||||
|
||||
func (c *networkInterfacesClient) collectNICs(ctx context.Context, pager apiPager[armnetwork.Interface]) ([]*NetworkInterface, error) {
|
||||
var nics []*NetworkInterface
|
||||
for pager.more() {
|
||||
res, err := pager.nextPage(ctx)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(ConvertResponseError(err))
|
||||
}
|
||||
for _, rawNIC := range res {
|
||||
if rawNIC == nil {
|
||||
continue
|
||||
}
|
||||
nic, err := nicFromArmNetworkInterface(rawNIC)
|
||||
if err != nil {
|
||||
c.logger.DebugContext(ctx, "Skipping Azure Network Interface",
|
||||
"resource_id", StringVal(rawNIC.ID),
|
||||
"error", err,
|
||||
)
|
||||
continue
|
||||
}
|
||||
nics = append(nics, nic)
|
||||
}
|
||||
}
|
||||
return nics, nil
|
||||
}
|
||||
|
||||
func nicFromArmNetworkInterface(nic *armnetwork.Interface) (*NetworkInterface, error) {
|
||||
if nic == nil {
|
||||
return nil, trace.BadParameter("nil armnetwork.Interface")
|
||||
}
|
||||
|
||||
var primary bool
|
||||
var attachedVMID string
|
||||
ipConfigs := []IPConfiguration{}
|
||||
if nic.Properties != nil {
|
||||
primary = BoolVal(nic.Properties.Primary)
|
||||
if nic.Properties.VirtualMachine != nil {
|
||||
attachedVMID = StringVal(nic.Properties.VirtualMachine.ID)
|
||||
}
|
||||
if nic.Properties.IPConfigurations != nil {
|
||||
for _, ipConfig := range nic.Properties.IPConfigurations {
|
||||
if ipConfig != nil && ipConfig.Properties != nil {
|
||||
ipConfigs = append(ipConfigs, IPConfiguration{
|
||||
Primary: BoolVal(ipConfig.Properties.Primary),
|
||||
PrivateIP: StringVal(ipConfig.Properties.PrivateIPAddress),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &NetworkInterface{
|
||||
ID: StringVal(nic.ID),
|
||||
Name: StringVal(nic.Name),
|
||||
Primary: primary,
|
||||
AttachedVMID: attachedVMID,
|
||||
IPConfigurations: ipConfigs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BoolVal converts a pointer of a bool to a bool value.
|
||||
func BoolVal(b *bool) bool {
|
||||
if b != nil {
|
||||
return *b
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestNetworkInterfacesClientLiveList exercises the network interfaces client
|
||||
// against a real Azure subscription, listing the standalone NICs (regular VMs
|
||||
// and flexible scale set VMs) in a resource group. It is skipped unless
|
||||
// TELEPORT_TEST_AZURE is set.
|
||||
//
|
||||
// Prerequisites:
|
||||
// - Authenticate so DefaultAzureCredential can find a credential, e.g. run
|
||||
// `az login`, or set AZURE_TENANT_ID / AZURE_CLIENT_ID / AZURE_CLIENT_SECRET.
|
||||
// The identity needs Microsoft.Network/networkInterfaces/read in the
|
||||
// resource group.
|
||||
// - Set AZURE_SUBSCRIPTION_ID to the subscription to list from.
|
||||
// - Set AZURE_RESOURCE_GROUP to a resource group with at least one NIC.
|
||||
//
|
||||
// Run with:
|
||||
//
|
||||
// TELEPORT_TEST_AZURE=1 \
|
||||
// AZURE_SUBSCRIPTION_ID=<sub> \
|
||||
// AZURE_RESOURCE_GROUP=<rg> \
|
||||
// go test ./lib/cloud/azure/ -run TestNetworkInterfacesClientLiveList -v
|
||||
func TestNetworkInterfacesClientLiveList(t *testing.T) {
|
||||
nicClient, resourceGroup := newLiveNetworkInterfacesClient(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
nics, err := nicClient.ListNetworkInterfaces(ctx, resourceGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
logNICs(t, nics)
|
||||
}
|
||||
|
||||
// TestNetworkInterfacesClientLiveListUniformVMSS exercises the network
|
||||
// interfaces client against a real Azure subscription, listing the NICs of
|
||||
// uniform scale set VMs alongside the standalone NICs in the resource group.
|
||||
// It is skipped unless TELEPORT_TEST_AZURE, TELEPORT_TEST_AZURE_SUBSCRIPTION_ID
|
||||
// and TELEPORT_TEST_AZURE_SCALE_SET_IDS are set.
|
||||
// Prerequisites are the same as TestNetworkInterfacesClientLiveList, plus:
|
||||
// - Set TELEPORT_TEST_AZURE_SCALE_SET_IDS to a comma-separated list of
|
||||
// uniform VM scale set IDs in the resource group, e.g. "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1,/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Compute/virtualMachineScaleSets/vmss2".
|
||||
//
|
||||
// Run with:
|
||||
//
|
||||
// TELEPORT_TEST_AZURE=1 \
|
||||
// AZURE_SUBSCRIPTION_ID=<sub> \
|
||||
// AZURE_RESOURCE_GROUP=<rg> \
|
||||
// TELEPORT_TEST_AZURE_SCALE_SET_IDS=<vmss1_id,vmss2_id> \
|
||||
// go test ./lib/cloud/azure/ -run TestNetworkInterfacesClientLiveListUniformVMSS -v
|
||||
func TestNetworkInterfacesClientLiveListUniformVMSS(t *testing.T) {
|
||||
nicClient, resourceGroup := newLiveNetworkInterfacesClient(t)
|
||||
|
||||
var scaleSetIDs []string
|
||||
for id := range strings.SplitSeq(os.Getenv("TELEPORT_TEST_AZURE_SCALE_SET_IDS"), ",") {
|
||||
if id = strings.TrimSpace(id); id != "" {
|
||||
scaleSetIDs = append(scaleSetIDs, id)
|
||||
}
|
||||
}
|
||||
if len(scaleSetIDs) == 0 {
|
||||
t.Skip("Set TELEPORT_TEST_AZURE_SCALE_SET_IDS to a comma-separated list of uniform VM scale set IDs.")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
nics, err := nicClient.ListNetworkInterfaces(ctx, resourceGroup, scaleSetIDs...)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Uniform VMSS NICs are attached to a scale set VM instance, so they can
|
||||
// be identified by their attached VM's resource ID.
|
||||
var uniformNICs []*NetworkInterface
|
||||
for _, nic := range nics {
|
||||
if strings.Contains(strings.ToLower(nic.AttachedVMID), "/virtualmachinescalesets/") {
|
||||
uniformNICs = append(uniformNICs, nic)
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, uniformNICs, "expected the named scale sets to have at least one NIC")
|
||||
|
||||
// A uniform VMSS NIC is a child resource of the scale set VM it is
|
||||
// attached to, so its ID must extend its attached VM's ID.
|
||||
for _, nic := range uniformNICs {
|
||||
require.True(t,
|
||||
strings.HasPrefix(strings.ToLower(nic.ID), strings.ToLower(nic.AttachedVMID)+"/"),
|
||||
"expected NIC %q to be a child of its attached VM %q", nic.ID, nic.AttachedVMID)
|
||||
}
|
||||
|
||||
logNICs(t, uniformNICs)
|
||||
}
|
||||
|
||||
// newLiveNetworkInterfacesClient builds a NetworkInterfacesClient backed by a
|
||||
// real Azure credential, skipping the test unless the live-test environment
|
||||
// variables are set.
|
||||
func newLiveNetworkInterfacesClient(t *testing.T) (NetworkInterfacesClient, string) {
|
||||
t.Helper()
|
||||
|
||||
if os.Getenv("TELEPORT_TEST_AZURE") == "" {
|
||||
t.Skip("Set TELEPORT_TEST_AZURE to run this test against a real Azure subscription.")
|
||||
}
|
||||
subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID")
|
||||
if subscriptionID == "" {
|
||||
t.Skip("Set AZURE_SUBSCRIPTION_ID to the subscription to list network interfaces from.")
|
||||
}
|
||||
resourceGroup := os.Getenv("AZURE_RESOURCE_GROUP")
|
||||
if resourceGroup == "" {
|
||||
t.Skip("Set AZURE_RESOURCE_GROUP to a resource group with at least one network interface.")
|
||||
}
|
||||
|
||||
cred, err := azidentity.NewDefaultAzureCredential(nil)
|
||||
require.NoError(t, err, "failed to build a default Azure credential; have you run `az login`?")
|
||||
|
||||
nicClient, err := NewNetworkInterfacesClient(subscriptionID, cred, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return nicClient, resourceGroup
|
||||
}
|
||||
|
||||
// logNICs logs the listed NICs so live-test runs can be verified by eye, and
|
||||
// sanity-checks the invariants every real Azure NIC satisfies.
|
||||
func logNICs(t *testing.T, nics []*NetworkInterface) {
|
||||
t.Helper()
|
||||
|
||||
t.Logf("listed %d NIC(s)", len(nics))
|
||||
for _, nic := range nics {
|
||||
require.NotEmpty(t, nic.ID)
|
||||
require.NotEmpty(t, nic.Name)
|
||||
t.Logf(" NIC %q: primary=%t attached VM=%q", nic.Name, nic.Primary, nic.AttachedVMID)
|
||||
for _, ipConfig := range nic.IPConfigurations {
|
||||
t.Logf(" ipconfig: primary=%t private IP=%q", ipConfig.Primary, ipConfig.PrivateIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/stretchr/testify/require"
|
||||
"pgregory.net/rapid"
|
||||
|
||||
"github.com/gravitational/teleport/lib/utils/log/logtest"
|
||||
)
|
||||
|
||||
// ipConfigGen returns a rapid.Generator that produces arbitrary Azure network
|
||||
// interface IP configurations.
|
||||
func ipConfigGen() *rapid.Generator[armnetwork.InterfaceIPConfiguration] {
|
||||
return rapid.Custom(func(t *rapid.T) armnetwork.InterfaceIPConfiguration {
|
||||
return armnetwork.InterfaceIPConfiguration{
|
||||
Name: rapid.Ptr(rapid.String(), true).Draw(t, "ipconfig_name"),
|
||||
Properties: rapid.Ptr(rapid.Custom(func(t *rapid.T) armnetwork.InterfaceIPConfigurationPropertiesFormat {
|
||||
return armnetwork.InterfaceIPConfigurationPropertiesFormat{
|
||||
PrivateIPAddress: rapid.Ptr(rapid.String(), true).Draw(t, "private_ip"),
|
||||
Primary: rapid.Ptr(rapid.Bool(), true).Draw(t, "ipconfig_primary"),
|
||||
}
|
||||
}), true).Draw(t, "ipconfig_props"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// nicGen returns a rapid.Generator that produces arbitrary Azure network
|
||||
// interfaces.
|
||||
func nicGen() *rapid.Generator[*armnetwork.Interface] {
|
||||
return rapid.Custom(func(t *rapid.T) *armnetwork.Interface {
|
||||
return &armnetwork.Interface{
|
||||
ID: rapid.Ptr(rapid.String(), true).Draw(t, "id"),
|
||||
Name: rapid.Ptr(rapid.String(), true).Draw(t, "name"),
|
||||
Properties: rapid.Ptr(rapid.Custom(func(t *rapid.T) armnetwork.InterfacePropertiesFormat {
|
||||
return armnetwork.InterfacePropertiesFormat{
|
||||
Primary: rapid.Ptr(rapid.Bool(), true).Draw(t, "primary"),
|
||||
VirtualMachine: rapid.Ptr(rapid.Custom(func(t *rapid.T) armnetwork.SubResource {
|
||||
return armnetwork.SubResource{
|
||||
ID: rapid.Ptr(rapid.String(), true).Draw(t, "vm_id"),
|
||||
}
|
||||
}), true).Draw(t, "virtual_machine"),
|
||||
IPConfigurations: rapid.SliceOfN(rapid.Ptr(ipConfigGen(), true), 0, 5).Draw(t, "ipconfigs"),
|
||||
}
|
||||
}), true).Draw(t, "props"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestProperty_nicFromArmNetworkInterface_NeverPanicsOnArbitraryInput ensures that
|
||||
// nicFromArmNetworkInterface never panics, even when given arbitrary input.
|
||||
func TestProperty_nicFromArmNetworkInterface_NeverPanicsOnArbitraryInput(t *testing.T) {
|
||||
rapid.Check(t, func(t *rapid.T) {
|
||||
nicGen := nicGen()
|
||||
|
||||
nic, err := nicFromArmNetworkInterface(nicGen.Draw(t, "nic"))
|
||||
require.NoError(t, err)
|
||||
|
||||
if nicGen == nil {
|
||||
require.Nil(t, nic)
|
||||
} else {
|
||||
require.NotNil(t, nic)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestProperty_nicFromArmNetworkInterface_FieldPassThrough ensures that all fields
|
||||
// of an Azure network interface are passed through to the Teleport representation
|
||||
// of the network interface.
|
||||
func TestProperty_nicFromArmNetworkInterface_FieldPassThrough(t *testing.T) {
|
||||
rapid.Check(t, func(t *rapid.T) {
|
||||
rawNIC := nicGen().Draw(t, "nic")
|
||||
|
||||
nic, err := nicFromArmNetworkInterface(rawNIC)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, nic)
|
||||
|
||||
if rawNIC.ID != nil {
|
||||
require.Equal(t, *rawNIC.ID, nic.ID)
|
||||
} else {
|
||||
require.Empty(t, nic.ID)
|
||||
}
|
||||
if rawNIC.Name != nil {
|
||||
require.Equal(t, *rawNIC.Name, nic.Name)
|
||||
} else {
|
||||
require.Empty(t, nic.Name)
|
||||
}
|
||||
|
||||
wantPrimary := rawNIC.Properties != nil &&
|
||||
rawNIC.Properties.Primary != nil &&
|
||||
*rawNIC.Properties.Primary
|
||||
require.Equal(t, wantPrimary, nic.Primary)
|
||||
|
||||
wantVMID := ""
|
||||
if rawNIC.Properties != nil &&
|
||||
rawNIC.Properties.VirtualMachine != nil &&
|
||||
rawNIC.Properties.VirtualMachine.ID != nil {
|
||||
wantVMID = *rawNIC.Properties.VirtualMachine.ID
|
||||
}
|
||||
require.Equal(t, wantVMID, nic.AttachedVMID)
|
||||
|
||||
if rawNIC.Properties != nil {
|
||||
outIdx := 0
|
||||
for _, ipConfig := range rawNIC.Properties.IPConfigurations {
|
||||
if ipConfig == nil || ipConfig.Properties == nil {
|
||||
continue
|
||||
}
|
||||
require.Less(t, outIdx, len(nic.IPConfigurations))
|
||||
got := nic.IPConfigurations[outIdx]
|
||||
outIdx++
|
||||
|
||||
if ipConfig.Properties.Primary != nil {
|
||||
require.Equal(t, *ipConfig.Properties.Primary, got.Primary)
|
||||
} else {
|
||||
require.False(t, got.Primary)
|
||||
}
|
||||
if ipConfig.Properties.PrivateIPAddress != nil {
|
||||
require.Equal(t, *ipConfig.Properties.PrivateIPAddress, got.PrivateIP)
|
||||
} else {
|
||||
require.Empty(t, got.PrivateIP)
|
||||
}
|
||||
}
|
||||
require.Len(t, nic.IPConfigurations, outIdx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestProperty_ListNetworkInterfaces_Aggregation ensures that
|
||||
// ListNetworkInterfaces correctly aggregates network interfaces from multiple
|
||||
// sources, including standalone network interfaces and those associated with
|
||||
// virtual machine scale sets.
|
||||
func TestProperty_ListNetworkInterfaces_Aggregation(t *testing.T) {
|
||||
rapid.Check(t, func(t *rapid.T) {
|
||||
standalone := rapid.SliceOfN(nicGen(), 0, 5).Draw(t, "standalone")
|
||||
scaleSets := rapid.MapOfN(
|
||||
rapid.StringMatching(`vmss-[a-z]{1,5}`),
|
||||
rapid.SliceOfN(nicGen(), 0, 3),
|
||||
0, 4,
|
||||
).Draw(t, "scale_sets")
|
||||
|
||||
vmssNICs := make(map[string][]*armnetwork.Interface, len(scaleSets))
|
||||
vmssErrs := make(map[string]error)
|
||||
var scaleSetIDs []string
|
||||
wantCount := len(standalone)
|
||||
for name, nics := range scaleSets {
|
||||
scaleSetIDs = append(scaleSetIDs, createVMSSID(rgName, name))
|
||||
vmssNICs[rgName+"/"+name] = nics
|
||||
if rapid.Bool().Draw(t, "fails_"+name) {
|
||||
vmssErrs[name] = trace.AccessDenied("unauthorized")
|
||||
} else {
|
||||
wantCount += len(nics)
|
||||
}
|
||||
}
|
||||
|
||||
client := NewNetworkInterfacesClientByAPI(NetworkInterfacesClientConfig{
|
||||
NetworkInterfacesAPI: &ARMNetworkMock{
|
||||
NetworkInterfaces: map[string][]*armnetwork.Interface{rgName: standalone},
|
||||
VMSSNetworkInterfaces: vmssNICs,
|
||||
VMSSListErrs: vmssErrs,
|
||||
},
|
||||
SubscriptionID: testSubID,
|
||||
})
|
||||
|
||||
got, err := client.ListNetworkInterfaces(t.Context(), rgName, scaleSetIDs...)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, wantCount)
|
||||
})
|
||||
}
|
||||
|
||||
// TestProperty_collectNICs_PageBoundaries ensures that collectNICs correctly
|
||||
// aggregates network interfaces across multiple pages of results.
|
||||
func TestProperty_collectNICs_PageBoundaries(t *testing.T) {
|
||||
rapid.Check(t, func(t *rapid.T) {
|
||||
nics := rapid.SliceOfN(
|
||||
rapid.OneOf(rapid.Just[*armnetwork.Interface](nil), nicGen()),
|
||||
0, 20,
|
||||
).Draw(t, "nics")
|
||||
|
||||
// Split the NICs into pages at arbitrary boundaries.
|
||||
var pages [][]*armnetwork.Interface
|
||||
for remaining := nics; len(remaining) > 0; {
|
||||
n := rapid.IntRange(1, len(remaining)).Draw(t, "page_size")
|
||||
pages = append(pages, remaining[:n])
|
||||
remaining = remaining[n:]
|
||||
}
|
||||
if len(pages) == 0 {
|
||||
// A pager always serves at least one empty page.
|
||||
pages = [][]*armnetwork.Interface{nil}
|
||||
}
|
||||
|
||||
pageIdx := 0
|
||||
pager := runtime.NewPager(runtime.PagingHandler[armnetwork.InterfacesClientListResponse]{
|
||||
More: func(page armnetwork.InterfacesClientListResponse) bool {
|
||||
return page.NextLink != nil && *page.NextLink != ""
|
||||
},
|
||||
Fetcher: func(_ context.Context, _ *armnetwork.InterfacesClientListResponse) (armnetwork.InterfacesClientListResponse, error) {
|
||||
page := pages[pageIdx]
|
||||
pageIdx++
|
||||
var nextLink *string
|
||||
if pageIdx < len(pages) {
|
||||
nextLink = to.Ptr("nextpage")
|
||||
}
|
||||
return armnetwork.InterfacesClientListResponse{
|
||||
InterfaceListResult: armnetwork.InterfaceListResult{
|
||||
Value: page,
|
||||
NextLink: nextLink,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
|
||||
c := &networkInterfacesClient{logger: logtest.NewLogger()}
|
||||
got, err := c.collectNICs(t.Context(), newAPIPager(pager, func(resp armnetwork.InterfacesClientListResponse) []*armnetwork.Interface {
|
||||
return resp.InterfaceListResult.Value
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check NICs appear converted and in order, no matter how the input was
|
||||
// split into pages. nicFromArmNetworkInterface is used as the oracle to
|
||||
// determine the expected output.
|
||||
var want []*NetworkInterface
|
||||
for _, rawNIC := range nics {
|
||||
if rawNIC == nil {
|
||||
continue
|
||||
}
|
||||
nic, err := nicFromArmNetworkInterface(rawNIC)
|
||||
require.NoError(t, err)
|
||||
want = append(want, nic)
|
||||
}
|
||||
require.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v7"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
)
|
||||
|
||||
func TestListNetworkInterfaces(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
scaleSet1 = "vmss1"
|
||||
scaleSet2 = "vmss2"
|
||||
)
|
||||
|
||||
regularNIC := &armnetwork.Interface{
|
||||
ID: to.Ptr(createNICID(rgName, "nic1")),
|
||||
Name: to.Ptr("nic1"),
|
||||
Properties: &armnetwork.InterfacePropertiesFormat{
|
||||
Primary: to.Ptr(true),
|
||||
VirtualMachine: &armnetwork.SubResource{ID: to.Ptr(createVMID(rgName, "vm1"))},
|
||||
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
|
||||
{
|
||||
Name: to.Ptr("ipconfig1"),
|
||||
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
|
||||
PrivateIPAddress: to.Ptr("10.0.0.1"),
|
||||
Primary: to.Ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
wantRegularNIC := &NetworkInterface{
|
||||
ID: createNICID(rgName, "nic1"),
|
||||
Name: "nic1",
|
||||
Primary: true,
|
||||
AttachedVMID: createVMID(rgName, "vm1"),
|
||||
IPConfigurations: []IPConfiguration{{
|
||||
Primary: true,
|
||||
PrivateIP: "10.0.0.1",
|
||||
}},
|
||||
}
|
||||
|
||||
multiIPConfigNIC := &armnetwork.Interface{
|
||||
ID: to.Ptr(createNICID(rgName, "nic2")),
|
||||
Name: to.Ptr("nic2"),
|
||||
Properties: &armnetwork.InterfacePropertiesFormat{
|
||||
Primary: to.Ptr(false),
|
||||
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
|
||||
{
|
||||
Name: to.Ptr("ipconfig1"),
|
||||
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
|
||||
PrivateIPAddress: to.Ptr("10.0.0.2"),
|
||||
Primary: to.Ptr(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: to.Ptr("ipconfig2"),
|
||||
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
|
||||
PrivateIPAddress: to.Ptr("10.0.0.128/25"),
|
||||
Primary: to.Ptr(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
// IP configuration without properties is skipped.
|
||||
Name: to.Ptr("ipconfig3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
wantMultiIPConfigNIC := &NetworkInterface{
|
||||
ID: createNICID(rgName, "nic2"),
|
||||
Name: "nic2",
|
||||
IPConfigurations: []IPConfiguration{
|
||||
{
|
||||
Primary: true,
|
||||
PrivateIP: "10.0.0.2",
|
||||
},
|
||||
{
|
||||
Primary: false,
|
||||
PrivateIP: "10.0.0.128/25",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// A NIC not attached to any VM has no Primary flag and may have no
|
||||
// properties at all.
|
||||
detachedNIC := &armnetwork.Interface{
|
||||
ID: to.Ptr(createNICID(rgName, "nic-detached")),
|
||||
Name: to.Ptr("nic-detached"),
|
||||
}
|
||||
wantDetachedNIC := &NetworkInterface{
|
||||
ID: createNICID(rgName, "nic-detached"),
|
||||
Name: "nic-detached",
|
||||
IPConfigurations: []IPConfiguration{},
|
||||
}
|
||||
|
||||
const otherRG = "rg2"
|
||||
otherRGNIC := &armnetwork.Interface{
|
||||
ID: to.Ptr(createNICID(otherRG, "nic-rg2")),
|
||||
Name: to.Ptr("nic-rg2"),
|
||||
}
|
||||
wantOtherRGNIC := &NetworkInterface{
|
||||
ID: createNICID(otherRG, "nic-rg2"),
|
||||
Name: "nic-rg2",
|
||||
IPConfigurations: []IPConfiguration{},
|
||||
}
|
||||
|
||||
scaleSet1NIC := &armnetwork.Interface{
|
||||
ID: to.Ptr(createVMSSNICID(rgName, scaleSet1, "0", "vmss1-nic")),
|
||||
Name: to.Ptr("vmss1-nic"),
|
||||
Properties: &armnetwork.InterfacePropertiesFormat{
|
||||
Primary: to.Ptr(true),
|
||||
VirtualMachine: &armnetwork.SubResource{ID: to.Ptr(createVMSSVMID(rgName, scaleSet1, "0"))},
|
||||
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
|
||||
{
|
||||
Name: to.Ptr("ipconfig1"),
|
||||
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
|
||||
PrivateIPAddress: to.Ptr("10.0.1.1"),
|
||||
Primary: to.Ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
wantScaleSet1NIC := &NetworkInterface{
|
||||
ID: createVMSSNICID(rgName, scaleSet1, "0", "vmss1-nic"),
|
||||
Name: "vmss1-nic",
|
||||
Primary: true,
|
||||
AttachedVMID: createVMSSVMID(rgName, scaleSet1, "0"),
|
||||
IPConfigurations: []IPConfiguration{{
|
||||
Primary: true,
|
||||
PrivateIP: "10.0.1.1",
|
||||
}},
|
||||
}
|
||||
|
||||
scaleSet2NIC := &armnetwork.Interface{
|
||||
ID: to.Ptr(createVMSSNICID(rgName, scaleSet2, "0", "vmss2-nic")),
|
||||
Name: to.Ptr("vmss2-nic"),
|
||||
Properties: &armnetwork.InterfacePropertiesFormat{
|
||||
Primary: to.Ptr(true),
|
||||
VirtualMachine: &armnetwork.SubResource{ID: to.Ptr(createVMSSVMID(rgName, scaleSet2, "0"))},
|
||||
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
|
||||
{
|
||||
Name: to.Ptr("ipconfig1"),
|
||||
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
|
||||
PrivateIPAddress: to.Ptr("10.0.2.1"),
|
||||
Primary: to.Ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
wantScaleSet2NIC := &NetworkInterface{
|
||||
ID: createVMSSNICID(rgName, scaleSet2, "0", "vmss2-nic"),
|
||||
Name: "vmss2-nic",
|
||||
Primary: true,
|
||||
AttachedVMID: createVMSSVMID(rgName, scaleSet2, "0"),
|
||||
IPConfigurations: []IPConfiguration{{
|
||||
Primary: true,
|
||||
PrivateIP: "10.0.2.1",
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
nicAPI *ARMNetworkMock
|
||||
scaleSetIDs []string
|
||||
resourceGroup string
|
||||
want []*NetworkInterface
|
||||
expectErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
desc: "NICs from regular VMs, no scale set NICs",
|
||||
nicAPI: &ARMNetworkMock{
|
||||
NetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName: {regularNIC},
|
||||
},
|
||||
},
|
||||
resourceGroup: rgName,
|
||||
want: []*NetworkInterface{wantRegularNIC},
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "resource group without NICs returns nothing",
|
||||
nicAPI: &ARMNetworkMock{},
|
||||
resourceGroup: rgName,
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "converts secondary IP configurations and detached NICs",
|
||||
nicAPI: &ARMNetworkMock{
|
||||
NetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName: {multiIPConfigNIC, detachedNIC},
|
||||
},
|
||||
},
|
||||
resourceGroup: rgName,
|
||||
want: []*NetworkInterface{wantMultiIPConfigNIC, wantDetachedNIC},
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "combines standalone and uniform scale set NICs",
|
||||
nicAPI: &ARMNetworkMock{
|
||||
NetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName: {regularNIC},
|
||||
},
|
||||
VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName + "/" + scaleSet1: {scaleSet1NIC},
|
||||
rgName + "/" + scaleSet2: {scaleSet2NIC},
|
||||
},
|
||||
},
|
||||
resourceGroup: rgName,
|
||||
scaleSetIDs: []string{createVMSSID(rgName, scaleSet1), createVMSSID(rgName, scaleSet2)},
|
||||
want: []*NetworkInterface{wantRegularNIC, wantScaleSet1NIC, wantScaleSet2NIC},
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "scale set NICs are only listed for named scale sets",
|
||||
nicAPI: &ARMNetworkMock{
|
||||
NetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName: {regularNIC},
|
||||
},
|
||||
VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName + "/" + scaleSet1: {scaleSet1NIC},
|
||||
},
|
||||
},
|
||||
resourceGroup: rgName,
|
||||
want: []*NetworkInterface{wantRegularNIC},
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "failure listing one scale set does not fail the rest",
|
||||
nicAPI: &ARMNetworkMock{
|
||||
NetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName: {regularNIC},
|
||||
},
|
||||
VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName + "/" + scaleSet1: {scaleSet1NIC},
|
||||
rgName + "/" + scaleSet2: {scaleSet2NIC},
|
||||
},
|
||||
VMSSListErrs: map[string]error{
|
||||
scaleSet1: trace.AccessDenied("unauthorized"),
|
||||
},
|
||||
},
|
||||
resourceGroup: rgName,
|
||||
scaleSetIDs: []string{createVMSSID(rgName, scaleSet1), createVMSSID(rgName, scaleSet2)},
|
||||
want: []*NetworkInterface{wantRegularNIC, wantScaleSet2NIC},
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "duplicate scale set IDs are only listed once",
|
||||
nicAPI: &ARMNetworkMock{
|
||||
VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName + "/" + scaleSet1: {scaleSet1NIC},
|
||||
},
|
||||
},
|
||||
resourceGroup: rgName,
|
||||
scaleSetIDs: []string{
|
||||
createVMSSID(rgName, scaleSet1),
|
||||
createVMSSID(rgName, scaleSet1),
|
||||
strings.ToUpper(createVMSSID(rgName, scaleSet1)),
|
||||
},
|
||||
want: []*NetworkInterface{wantScaleSet1NIC},
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "invalid scale set IDs are skipped",
|
||||
nicAPI: &ARMNetworkMock{
|
||||
NetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName: {regularNIC},
|
||||
},
|
||||
VMSSNetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName + "/" + scaleSet1: {scaleSet1NIC},
|
||||
},
|
||||
},
|
||||
resourceGroup: rgName,
|
||||
scaleSetIDs: []string{
|
||||
"not-a-resource-id",
|
||||
createVMID(rgName, "vm1"),
|
||||
createVMSSVMID(rgName, scaleSet1, "0"),
|
||||
strings.ReplaceAll(createVMSSID(rgName, scaleSet1), testSubID, "22222222-2222-2222-2222-222222222222"),
|
||||
createVMSSID(rgName, scaleSet1),
|
||||
},
|
||||
want: []*NetworkInterface{wantRegularNIC, wantScaleSet1NIC},
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "nil NICs in the response are skipped",
|
||||
nicAPI: &ARMNetworkMock{
|
||||
NetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName: {nil, regularNIC},
|
||||
},
|
||||
},
|
||||
resourceGroup: rgName,
|
||||
want: []*NetworkInterface{wantRegularNIC},
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "auth error fails the whole listing",
|
||||
nicAPI: &ARMNetworkMock{NoAuth: true},
|
||||
resourceGroup: rgName,
|
||||
expectErr: func(t require.TestingT, err error, i ...any) {
|
||||
require.True(t, trace.IsAccessDenied(err), "expected AccessDenied error, got %v", err)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "wildcard resource group lists NICs from all resource groups",
|
||||
nicAPI: &ARMNetworkMock{
|
||||
NetworkInterfaces: map[string][]*armnetwork.Interface{
|
||||
rgName: {regularNIC},
|
||||
otherRG: {otherRGNIC},
|
||||
},
|
||||
},
|
||||
resourceGroup: types.Wildcard,
|
||||
want: []*NetworkInterface{wantRegularNIC, wantOtherRGNIC},
|
||||
expectErr: require.NoError,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := NewNetworkInterfacesClientByAPI(NetworkInterfacesClientConfig{
|
||||
NetworkInterfacesAPI: tc.nicAPI,
|
||||
SubscriptionID: testSubID,
|
||||
})
|
||||
|
||||
nics, err := client.ListNetworkInterfaces(
|
||||
t.Context(),
|
||||
tc.resourceGroup,
|
||||
tc.scaleSetIDs...,
|
||||
)
|
||||
tc.expectErr(t, err)
|
||||
require.ElementsMatch(t, tc.want, nics)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createNICID(resourceGroup, nicName string) string {
|
||||
return "/subscriptions/" + testSubID + "/resourceGroups/" + resourceGroup + "/providers/Microsoft.Network/networkInterfaces/" + nicName
|
||||
}
|
||||
|
||||
func createVMID(resourceGroup, vmName string) string {
|
||||
return "/subscriptions/" + testSubID + "/resourceGroups/" + resourceGroup + "/providers/Microsoft.Compute/virtualMachines/" + vmName
|
||||
}
|
||||
|
||||
func createVMSSID(resourceGroup, scaleSetName string) string {
|
||||
return "/subscriptions/" + testSubID + "/resourceGroups/" + resourceGroup + "/providers/Microsoft.Compute/virtualMachineScaleSets/" + scaleSetName
|
||||
}
|
||||
|
||||
func createVMSSVMID(resourceGroup, scaleSetName, instanceID string) string {
|
||||
return createVMSSID(resourceGroup, scaleSetName) + "/virtualMachines/" + instanceID
|
||||
}
|
||||
|
||||
func createVMSSNICID(resourceGroup, scaleSetName, instanceID, nicName string) string {
|
||||
return createVMSSVMID(resourceGroup, scaleSetName, instanceID) + "/networkInterfaces/" + nicName
|
||||
}
|
||||
Reference in New Issue
Block a user