mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-19 01:58:44 +08:00
Refactor Azure VM discovery metadata and status (#67590)
* Refactor Azure VM discovery metadata and status Move immutable Azure VM discovery metadata into a dedicated embedded AzureInstancesMetadata type. This lets enrollment, status, audit, usage event, and user-task paths pass metadata separately from the mutable Instances slice, which is filtered during enrollment. Change the resource status from a primitive map type into a struct. This will make the status map more flexible for further changes that introduce parallel installation and status updates. This also logs the instance count after filtering enrolled instances. * Update AzureInstances godoc * Make metadata an unembedded field * rename loop var from group to status * revert failed azure enrollment func to lambda * re-eval VMs log value iff some VMs were filtered
This commit is contained in:
@@ -484,6 +484,13 @@ func (c *runCommandClient) Run(ctx context.Context, req RunCommandRequest) (*Run
|
||||
runCommand := armcompute.VirtualMachineRunCommand{
|
||||
Location: to.Ptr(req.Region),
|
||||
Properties: &armcompute.VirtualMachineRunCommandProperties{
|
||||
// NOTE: The AsyncExecution option can be very misleading.
|
||||
// It has no effect on whether BeginCreateOrUpdate blocks.
|
||||
// Instead, it affects poller.PollUntilDone.
|
||||
// If set to true, then calling poller.PollUntilDone will actually
|
||||
// return as soon as the script is "provisioned" even if
|
||||
// the script execution state is still "running".
|
||||
// We always want this option set to false.
|
||||
AsyncExecution: to.Ptr(false),
|
||||
Source: &armcompute.VirtualMachineRunCommandScriptSource{
|
||||
Script: to.Ptr(req.Script),
|
||||
|
||||
@@ -23,8 +23,16 @@ import (
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
|
||||
"github.com/gravitational/teleport/api/types/usertasks"
|
||||
"github.com/gravitational/teleport/lib/srv/server"
|
||||
)
|
||||
|
||||
func classifyAzureInstallResultIssue(installResult server.AzureInstallResult) string {
|
||||
if installResult.CommandResult != nil {
|
||||
return usertasks.AutoDiscoverAzureVMIssueEnrollmentError
|
||||
}
|
||||
return classifyAzureVMEnrollmentError(installResult.APIError)
|
||||
}
|
||||
|
||||
// classifyAzureVMEnrollmentError classifies Azure API errors into user-facing
|
||||
// messages for VM auto-discovery. This is best-effort based on error strings
|
||||
// which may change without notice. The matching logic may require future
|
||||
|
||||
@@ -455,7 +455,7 @@ type Server struct {
|
||||
awsEC2Tasks awsEC2Tasks
|
||||
awsEKSTasks awsEKSTasks
|
||||
awsRDSTasks awsRDSTasks
|
||||
azureVMStatus atomic.Pointer[resourceStatusMap]
|
||||
azureVMStatus atomic.Pointer[discoveryStatus]
|
||||
|
||||
// caRotationCh receives nodes that need to have their CAs rotated.
|
||||
caRotationCh chan []types.Server
|
||||
@@ -1456,9 +1456,9 @@ func (s *Server) startAWSServerDiscovery() {
|
||||
go s.watchCARotation(s.ctx)
|
||||
}
|
||||
|
||||
func (s *Server) emitAzureInstallEvents(log *slog.Logger, instances *server.AzureInstances, result server.AzureInstallResult) {
|
||||
func (s *Server) emitAzureInstallEvents(log *slog.Logger, md server.AzureInstancesMetadata, result server.AzureInstallResult) {
|
||||
// emit run event.
|
||||
runEvent := instances.MakeRunEvent(result)
|
||||
runEvent := md.MakeRunEvent(result)
|
||||
err := s.Emitter.EmitAuditEvent(s.ctx, runEvent)
|
||||
if err != nil {
|
||||
log.WarnContext(s.ctx, "Failed to emit audit event", "error", err)
|
||||
@@ -1469,7 +1469,7 @@ func (s *Server) emitAzureInstallEvents(log *slog.Logger, instances *server.Azur
|
||||
}
|
||||
|
||||
// on success, emit usage event.
|
||||
vmKey, usageEvent := instances.MakeUsageEvent(result.Instance)
|
||||
vmKey, usageEvent := md.MakeUsageEvent(result.Instance)
|
||||
err = s.emitUsageEvent(vmKey, usageEvent)
|
||||
if err != nil {
|
||||
log.WarnContext(s.ctx, "Failed to emit usage event", "error", err)
|
||||
@@ -1533,12 +1533,12 @@ func (e *limitedErrorReporter) summary(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) enrollAzureVirtualMachines(log *slog.Logger, instances *server.AzureInstances) ([]server.AzureInstallResult, error) {
|
||||
azureClients, err := s.getAzureClients(s.ctx, instances.Integration)
|
||||
azureClients, err := s.getAzureClients(s.ctx, instances.Metadata.Integration)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
runClient, err := azureClients.GetRunCommandClient(s.ctx, instances.SubscriptionID)
|
||||
runClient, err := azureClients.GetRunCommandClient(s.ctx, instances.Metadata.SubscriptionID)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
@@ -1554,12 +1554,12 @@ func (s *Server) enrollAzureVirtualMachines(log *slog.Logger, instances *server.
|
||||
|
||||
req := server.AzureInstallRequest{
|
||||
Instances: instances.Instances,
|
||||
Region: instances.Region,
|
||||
ResourceGroup: instances.ResourceGroup,
|
||||
InstallerParams: instances.InstallerParams,
|
||||
Region: instances.Metadata.Region,
|
||||
ResourceGroup: instances.Metadata.ResourceGroup,
|
||||
InstallerParams: instances.Metadata.InstallerParams,
|
||||
ProxyAddrGetter: s.publicProxyAddress,
|
||||
OnRunCommandFinished: func(result server.AzureInstallResult) {
|
||||
s.emitAzureInstallEvents(log, instances, result)
|
||||
s.emitAzureInstallEvents(log, instances.Metadata, result)
|
||||
if result.Failure() {
|
||||
reporter.report(s.ctx, result)
|
||||
|
||||
@@ -1616,7 +1616,7 @@ func (s *Server) startAzureServerDiscovery() {
|
||||
azureWatcher.ReplaceFetchers(replaceMap)
|
||||
}
|
||||
|
||||
var sm *resourceStatusMap
|
||||
var sm *discoveryStatus
|
||||
var vmTasks *azureVMTasks
|
||||
var runStart time.Time
|
||||
|
||||
@@ -1639,22 +1639,22 @@ func (s *Server) startAzureServerDiscovery() {
|
||||
// "0 found/enrolled/failed" update instead of leaving stale non-zero status from a
|
||||
// previous iteration.
|
||||
for _, fetcher := range fetchers {
|
||||
fgKey := fetcherGroupKey{
|
||||
key := discoveryGroupStatusKey{
|
||||
discoveryConfigName: fetcher.GetDiscoveryConfigName(),
|
||||
integration: fetcher.IntegrationName(),
|
||||
}
|
||||
sm.add(fgKey, make(map[statusType]int))
|
||||
sm.add(key, discoveryGroupStatus{})
|
||||
}
|
||||
s.updateDiscoveryConfigStatus(sm.discoveryConfigs()...)
|
||||
}),
|
||||
server.WithPerInstanceHookFn(func(instanceGroups []*server.AzureInstances) {
|
||||
for _, group := range instanceGroups {
|
||||
fgKey := fetcherGroupKey{
|
||||
discoveryConfigName: group.DiscoveryConfigName,
|
||||
integration: group.Integration,
|
||||
key := discoveryGroupStatusKey{
|
||||
discoveryConfigName: group.Metadata.DiscoveryConfigName,
|
||||
integration: group.Metadata.Integration,
|
||||
}
|
||||
results := s.installAzureServers(group, vmTasks)
|
||||
sm.add(fgKey, results)
|
||||
status := s.installAzureServers(group, vmTasks)
|
||||
sm.add(key, status)
|
||||
}
|
||||
}),
|
||||
server.WithPostFetchHookFn[*server.AzureInstances](func() {
|
||||
@@ -1681,94 +1681,95 @@ func (s *Server) startAzureServerDiscovery() {
|
||||
go azureWatcher.Run()
|
||||
}
|
||||
|
||||
func (s *Server) installAzureServers(instances *server.AzureInstances, vmTasks *azureVMTasks) (results map[statusType]int) {
|
||||
results = make(map[statusType]int)
|
||||
|
||||
func (s *Server) installAzureServers(instances *server.AzureInstances, vmTasks *azureVMTasks) discoveryGroupStatus {
|
||||
var status discoveryGroupStatus
|
||||
log := s.Log.With("group", instances)
|
||||
log.DebugContext(s.ctx, "Processing instance group")
|
||||
|
||||
allFound := len(instances.Instances)
|
||||
results[statusFound] = allFound
|
||||
|
||||
if allFound == 0 {
|
||||
found := len(instances.Instances)
|
||||
if found == 0 {
|
||||
log.DebugContext(s.ctx, "No Azure instances found, skipping installation")
|
||||
return
|
||||
return status
|
||||
}
|
||||
status.found += found
|
||||
|
||||
nodes, err := s.nodeWatcher.CurrentResources(s.ctx)
|
||||
if err != nil {
|
||||
log.WarnContext(s.ctx, "Failed to get current node resources", "error", err)
|
||||
return
|
||||
return status
|
||||
}
|
||||
instances.FilterExistingNodes(nodes)
|
||||
|
||||
// count machines that have already been enrolled in previous cycles.
|
||||
needInstall := len(instances.Instances)
|
||||
results[statusEnrolled] = allFound - needInstall
|
||||
enrolled := found - needInstall
|
||||
if enrolled > 0 {
|
||||
status.enrolled += enrolled
|
||||
log.DebugContext(s.ctx, "Filtered out Azure instances that have already been enrolled",
|
||||
"enrolled", enrolled,
|
||||
)
|
||||
// re-evaluate the instances log value after filtering
|
||||
log = s.Log.With("group", instances)
|
||||
}
|
||||
|
||||
if len(instances.Instances) == 0 {
|
||||
log.DebugContext(s.ctx, "No Azure instances remain to enroll, skipping installation")
|
||||
return
|
||||
return status
|
||||
}
|
||||
|
||||
addFailedEnrollment := func(vm *azure.VirtualMachine, issueType string) {
|
||||
addFailedAzureEnrollment := func(vm *azure.VirtualMachine, issueType string) {
|
||||
// Static matchers don't have a discovery config resource, so skip creating user tasks
|
||||
// because validation requires a discovery config name.
|
||||
if instances.DiscoveryConfigName == noDiscoveryConfig {
|
||||
if instances.Metadata.DiscoveryConfigName == noDiscoveryConfig {
|
||||
return
|
||||
}
|
||||
|
||||
tg := usertasks.TaskGroup{
|
||||
Integration: instances.Integration,
|
||||
Integration: instances.Metadata.Integration,
|
||||
IssueType: issueType,
|
||||
}
|
||||
vmTasks.addFailedEnrollment(
|
||||
tg,
|
||||
azureVMTaskKey{
|
||||
subscriptionID: instances.SubscriptionID,
|
||||
resourceGroup: instances.ResourceGroup,
|
||||
region: instances.Region,
|
||||
subscriptionID: instances.Metadata.SubscriptionID,
|
||||
resourceGroup: instances.Metadata.ResourceGroup,
|
||||
region: instances.Metadata.Region,
|
||||
},
|
||||
usertasksv1.DiscoverAzureVMInstance_builder{
|
||||
VmId: vm.VMID,
|
||||
ResourceId: vm.ID,
|
||||
Name: vm.Name,
|
||||
DiscoveryConfig: instances.DiscoveryConfigName,
|
||||
DiscoveryConfig: instances.Metadata.DiscoveryConfigName,
|
||||
DiscoveryGroup: s.DiscoveryGroup,
|
||||
SyncTime: timestamppb.New(s.clock.Now()),
|
||||
}.Build(),
|
||||
)
|
||||
}
|
||||
|
||||
log.DebugContext(s.ctx, "Running Teleport installation on virtual machines", "group", instances, "vms", genAzureInstancesLogStr(instances.Instances))
|
||||
log.DebugContext(s.ctx, "Running Teleport installation on virtual machines", "vms", genAzureInstancesLogStr(instances.Instances))
|
||||
failures, err := s.enrollAzureVirtualMachines(log, instances)
|
||||
if err != nil {
|
||||
// treat non-nil err as deployment failure affecting all machines.
|
||||
log.WarnContext(s.ctx, "Failed to enroll discovered Azure VMs", "error", err, "count", len(instances.Instances))
|
||||
results[statusFailed] = len(instances.Instances)
|
||||
log.WarnContext(s.ctx, "Failed to enroll all discovered Azure VMs", "error", err)
|
||||
status.failed += len(instances.Instances)
|
||||
|
||||
issueType := classifyAzureVMEnrollmentError(err)
|
||||
for _, vm := range instances.Instances {
|
||||
addFailedEnrollment(vm, issueType)
|
||||
addFailedAzureEnrollment(vm, issueType)
|
||||
}
|
||||
return
|
||||
return status
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
log.WarnContext(s.ctx, "Failed to enroll some discovered Azure VMs", "count", len(failures))
|
||||
log.WarnContext(s.ctx, "Failed to enroll some discovered Azure VMs", "failures", len(failures))
|
||||
}
|
||||
|
||||
// count individual failed enrollments.
|
||||
results[statusFailed] = len(failures)
|
||||
status.failed += len(failures)
|
||||
|
||||
// Record failures as user tasks.
|
||||
for _, result := range failures {
|
||||
if result.CommandResult != nil {
|
||||
// TODO (Tener): check exit codes and create more detailed user tasks.
|
||||
addFailedEnrollment(result.Instance, usertasks.AutoDiscoverAzureVMIssueEnrollmentError)
|
||||
} else {
|
||||
addFailedEnrollment(result.Instance, classifyAzureVMEnrollmentError(result.APIError))
|
||||
}
|
||||
// TODO (Tener): check exit codes and create more detailed user tasks.
|
||||
addFailedAzureEnrollment(result.Instance, classifyAzureInstallResultIssue(result))
|
||||
}
|
||||
|
||||
pendingCount := len(instances.Instances) - len(failures)
|
||||
@@ -1780,8 +1781,7 @@ func (s *Server) installAzureServers(instances *server.AzureInstances, vmTasks *
|
||||
// There is no easy way to close that gap in the current architecture.
|
||||
log.DebugContext(s.ctx, "Installation attempt finished. If the machines have joined the cluster successfully, they will be counted as enrolled during the next iteration.", "pending", pendingCount)
|
||||
}
|
||||
|
||||
return
|
||||
return status
|
||||
}
|
||||
|
||||
func (s *Server) filterExistingGCPNodes(instances *server.GCPInstances) error {
|
||||
|
||||
+31
-35
@@ -1071,66 +1071,62 @@ func (s *taskUpdater) mergeAzure(oldSpec *usertasksv1.UserTaskSpec, newSpec *use
|
||||
mergeExistingInstances(s, oldSpec.GetDiscoverAzureVm().GetInstances(), newSpec.GetDiscoverAzureVm().GetInstances())
|
||||
}
|
||||
|
||||
type statusType int
|
||||
|
||||
const (
|
||||
statusFound statusType = iota
|
||||
statusEnrolled
|
||||
statusFailed
|
||||
)
|
||||
|
||||
type fetcherGroupKey struct {
|
||||
type discoveryGroupStatusKey struct {
|
||||
discoveryConfigName string
|
||||
integration string
|
||||
}
|
||||
|
||||
// resourceStatusMap tracks discovery status (found/enrolled/failed counts)
|
||||
// per fetcher group key (discovery config + integration combination).
|
||||
type resourceStatusMap struct {
|
||||
type discoveryGroupStatus struct {
|
||||
found int
|
||||
enrolled int
|
||||
failed int
|
||||
}
|
||||
|
||||
// discoveryStatus tracks discovery status (found/enrolled/failed counts) per
|
||||
// discovery group key (discovery config + integration combination).
|
||||
type discoveryStatus struct {
|
||||
resourceType string
|
||||
results map[fetcherGroupKey]map[statusType]int
|
||||
statuses map[discoveryGroupStatusKey]*discoveryGroupStatus
|
||||
syncStart *time.Time
|
||||
syncEnd *time.Time
|
||||
}
|
||||
|
||||
func newStatusMap(resourceType string, syncStart time.Time) *resourceStatusMap {
|
||||
return &resourceStatusMap{
|
||||
func newStatusMap(resourceType string, syncStart time.Time) *discoveryStatus {
|
||||
return &discoveryStatus{
|
||||
resourceType: resourceType,
|
||||
results: make(map[fetcherGroupKey]map[statusType]int),
|
||||
statuses: make(map[discoveryGroupStatusKey]*discoveryGroupStatus),
|
||||
syncStart: &syncStart,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *resourceStatusMap) syncEnded(syncEnd time.Time) {
|
||||
func (s *discoveryStatus) syncEnded(syncEnd time.Time) {
|
||||
s.syncEnd = &syncEnd
|
||||
}
|
||||
|
||||
func (s *resourceStatusMap) add(key fetcherGroupKey, results map[statusType]int) {
|
||||
if s.results[key] == nil {
|
||||
s.results[key] = make(map[statusType]int)
|
||||
}
|
||||
for k, v := range results {
|
||||
s.results[key][k] += v
|
||||
func (s *discoveryStatus) add(key discoveryGroupStatusKey, update discoveryGroupStatus) {
|
||||
if s.statuses[key] == nil {
|
||||
s.statuses[key] = &update
|
||||
return
|
||||
}
|
||||
status := s.statuses[key]
|
||||
status.found += update.found
|
||||
status.enrolled += update.enrolled
|
||||
status.failed += update.failed
|
||||
}
|
||||
|
||||
func (s *resourceStatusMap) mergeIntoGlobalStatus(discoveryConfigName string, existingStatus discoveryconfig.Status) discoveryconfig.Status {
|
||||
func (s *discoveryStatus) mergeIntoGlobalStatus(discoveryConfigName string, existingStatus discoveryconfig.Status) discoveryconfig.Status {
|
||||
if s == nil {
|
||||
// nil resourceStatusMap is valid, just empty.
|
||||
return existingStatus
|
||||
}
|
||||
|
||||
for key, results := range s.results {
|
||||
for key, status := range s.statuses {
|
||||
if key.discoveryConfigName != discoveryConfigName {
|
||||
continue
|
||||
}
|
||||
|
||||
if results == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update global discovered resources count.
|
||||
existingStatus.DiscoveredResources = existingStatus.DiscoveredResources + uint64(results[statusFound])
|
||||
existingStatus.DiscoveredResources += uint64(status.found)
|
||||
|
||||
// Initialize map if needed.
|
||||
if existingStatus.IntegrationDiscoveredResources == nil {
|
||||
@@ -1153,9 +1149,9 @@ func (s *resourceStatusMap) mergeIntoGlobalStatus(discoveryConfigName string, ex
|
||||
}
|
||||
|
||||
resourcesSummary := discoveryconfigv1.ResourcesDiscoveredSummary_builder{
|
||||
Found: uint64(results[statusFound]),
|
||||
Enrolled: uint64(results[statusEnrolled]),
|
||||
Failed: uint64(results[statusFailed]),
|
||||
Found: uint64(status.found),
|
||||
Enrolled: uint64(status.enrolled),
|
||||
Failed: uint64(status.failed),
|
||||
SyncStart: syncStart,
|
||||
SyncEnd: syncEnd,
|
||||
}.Build()
|
||||
@@ -1168,13 +1164,13 @@ func (s *resourceStatusMap) mergeIntoGlobalStatus(discoveryConfigName string, ex
|
||||
return existingStatus
|
||||
}
|
||||
|
||||
func (s *resourceStatusMap) discoveryConfigs() []string {
|
||||
func (s *discoveryStatus) discoveryConfigs() []string {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
names := map[string]struct{}{}
|
||||
for key := range s.results {
|
||||
for key := range s.statuses {
|
||||
names[key.discoveryConfigName] = struct{}{}
|
||||
}
|
||||
return slices.Collect(maps.Keys(names))
|
||||
|
||||
@@ -38,8 +38,8 @@ import (
|
||||
|
||||
const azureEventPrefix = "azure/"
|
||||
|
||||
// AzureInstances contains information about discovered Azure virtual machines.
|
||||
type AzureInstances struct {
|
||||
// AzureInstancesMetadata contains information about discovered Azure virtual machines.
|
||||
type AzureInstancesMetadata struct {
|
||||
// DiscoveryConfigName is the name of discovery config.
|
||||
DiscoveryConfigName string
|
||||
// Integration is the optional name of the integration to use for auth.
|
||||
@@ -54,43 +54,37 @@ type AzureInstances struct {
|
||||
|
||||
// InstallerParams are the installer parameters used for installation.
|
||||
InstallerParams *types.InstallerParams
|
||||
// Instances is a list of discovered Azure virtual machines.
|
||||
Instances []*azure.VirtualMachine
|
||||
}
|
||||
|
||||
func (instances *AzureInstances) LogValue() slog.Value {
|
||||
if instances == nil {
|
||||
return slog.StringValue("<nil>")
|
||||
}
|
||||
func (md AzureInstancesMetadata) LogValue() slog.Value {
|
||||
return slog.GroupValue(
|
||||
slog.Int("total_instances", len(instances.Instances)),
|
||||
slog.String("discovery_config", instances.DiscoveryConfigName),
|
||||
slog.String("integration", instances.Integration),
|
||||
slog.String("region", instances.Region),
|
||||
slog.String("resource_group", instances.ResourceGroup),
|
||||
slog.String("subscription_id", instances.SubscriptionID),
|
||||
slog.String("discovery_config", md.DiscoveryConfigName),
|
||||
slog.String("integration", md.Integration),
|
||||
slog.String("region", md.Region),
|
||||
slog.String("resource_group", md.ResourceGroup),
|
||||
slog.String("subscription_id", md.SubscriptionID),
|
||||
)
|
||||
}
|
||||
|
||||
func (instances *AzureInstances) resourceType() string {
|
||||
if instances.InstallerParams != nil && instances.InstallerParams.ScriptName == installers.InstallerScriptNameAgentless {
|
||||
func (md *AzureInstancesMetadata) resourceType() string {
|
||||
if md.InstallerParams != nil && md.InstallerParams.ScriptName == installers.InstallerScriptNameAgentless {
|
||||
return types.DiscoveredResourceAgentlessNode
|
||||
}
|
||||
return types.DiscoveredResourceNode
|
||||
}
|
||||
|
||||
// MakeUsageEvent builds usage event for a single installation result.
|
||||
func (instances *AzureInstances) MakeUsageEvent(instance *azure.VirtualMachine) (string, *usageeventsv1.ResourceCreateEvent) {
|
||||
func (md *AzureInstancesMetadata) MakeUsageEvent(instance *azure.VirtualMachine) (string, *usageeventsv1.ResourceCreateEvent) {
|
||||
return azureEventPrefix + instance.ID, &usageeventsv1.ResourceCreateEvent{
|
||||
ResourceType: instances.resourceType(),
|
||||
ResourceType: md.resourceType(),
|
||||
ResourceOrigin: types.OriginCloud,
|
||||
CloudProvider: types.CloudAzure,
|
||||
DiscoveryConfigName: instances.DiscoveryConfigName,
|
||||
DiscoveryConfigName: md.DiscoveryConfigName,
|
||||
}
|
||||
}
|
||||
|
||||
// MakeRunEvent builds run event for a single command run.
|
||||
func (instances *AzureInstances) MakeRunEvent(result AzureInstallResult) *apievents.AzureRun {
|
||||
func (md *AzureInstancesMetadata) MakeRunEvent(result AzureInstallResult) *apievents.AzureRun {
|
||||
eventCode := libevents.AzureRunSuccessCode
|
||||
|
||||
if result.Failure() {
|
||||
@@ -110,10 +104,10 @@ func (instances *AzureInstances) MakeRunEvent(result AzureInstallResult) *apieve
|
||||
Code: eventCode,
|
||||
},
|
||||
AzureMetadata: apievents.AzureMetadata{
|
||||
SubscriptionID: instances.SubscriptionID,
|
||||
ResourceGroup: instances.ResourceGroup,
|
||||
SubscriptionID: md.SubscriptionID,
|
||||
ResourceGroup: md.ResourceGroup,
|
||||
ResourceID: resourceID,
|
||||
Region: instances.Region,
|
||||
Region: md.Region,
|
||||
},
|
||||
AzureVMMetadata: apievents.AzureVMMetadata{
|
||||
VMID: vmID,
|
||||
@@ -143,13 +137,30 @@ func (instances *AzureInstances) MakeRunEvent(result AzureInstallResult) *apieve
|
||||
return evt
|
||||
}
|
||||
|
||||
// AzureInstances contains a list of discovered Azure virtual machines and
|
||||
// metadata.
|
||||
type AzureInstances struct {
|
||||
Metadata AzureInstancesMetadata
|
||||
|
||||
// Instances is a list of discovered Azure virtual machines.
|
||||
Instances []*azure.VirtualMachine
|
||||
}
|
||||
|
||||
// LogValue implements [slog.LogValuer].
|
||||
func (instances *AzureInstances) LogValue() slog.Value {
|
||||
return slog.GroupValue(
|
||||
slog.Int("count", len(instances.Instances)),
|
||||
slog.Any("metadata", instances.Metadata),
|
||||
)
|
||||
}
|
||||
|
||||
// FilterExistingNodes removes instances matching existing nodes in place.
|
||||
func (instances *AzureInstances) FilterExistingNodes(existingNodes []types.Server) {
|
||||
vmIDs := make(map[string]struct{})
|
||||
for _, node := range existingNodes {
|
||||
labels := node.GetAllLabels()
|
||||
subscriptionID := labels[types.SubscriptionIDLabelInternal]
|
||||
if subscriptionID != instances.SubscriptionID {
|
||||
if subscriptionID != instances.Metadata.SubscriptionID {
|
||||
continue
|
||||
}
|
||||
vmID := labels[types.VMIDLabelInternal]
|
||||
@@ -320,13 +331,15 @@ func (f *azureInstanceFetcher) GetInstances(ctx context.Context, _ bool) ([]*Azu
|
||||
var instances []*AzureInstances
|
||||
for batchGroup, vms := range instanceGroups {
|
||||
instances = append(instances, &AzureInstances{
|
||||
SubscriptionID: f.Subscription,
|
||||
Region: batchGroup.location,
|
||||
ResourceGroup: batchGroup.resourceGroup,
|
||||
Instances: vms,
|
||||
Integration: f.Integration,
|
||||
InstallerParams: f.InstallerParams,
|
||||
DiscoveryConfigName: f.DiscoveryConfigName,
|
||||
Metadata: AzureInstancesMetadata{
|
||||
SubscriptionID: f.Subscription,
|
||||
Region: batchGroup.location,
|
||||
ResourceGroup: batchGroup.resourceGroup,
|
||||
Integration: f.Integration,
|
||||
InstallerParams: f.InstallerParams,
|
||||
DiscoveryConfigName: f.DiscoveryConfigName,
|
||||
},
|
||||
Instances: vms,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -276,8 +276,8 @@ func TestAzureWatcher(t *testing.T) {
|
||||
vmID := parsedResource.Name
|
||||
vmIDs = append(vmIDs, vmID)
|
||||
}
|
||||
require.NotEqual(t, "*", results.ResourceGroup, "Discovered VM's ResourceGroup should never be the wildcard")
|
||||
require.NotEqual(t, "*", results.SubscriptionID, "Discovered VM's SubscriptionID should never be the wildcard")
|
||||
require.NotEqual(t, "*", results.Metadata.ResourceGroup, "Discovered VM's ResourceGroup should never be the wildcard")
|
||||
require.NotEqual(t, "*", results.Metadata.SubscriptionID, "Discovered VM's SubscriptionID should never be the wildcard")
|
||||
case <-ctx.Done():
|
||||
require.ElementsMatch(t, tc.wantVMs, vmIDs, "timed out while waiting for expected VMs")
|
||||
}
|
||||
@@ -300,7 +300,9 @@ func TestAzureInstances_FilterExistingNodes(t *testing.T) {
|
||||
{
|
||||
name: "no existing nodes",
|
||||
instances: &AzureInstances{
|
||||
SubscriptionID: "sub-1",
|
||||
Metadata: AzureInstancesMetadata{
|
||||
SubscriptionID: "sub-1",
|
||||
},
|
||||
Instances: []*azure.VirtualMachine{
|
||||
{
|
||||
ID: "/subscriptions/sub-1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
||||
@@ -318,7 +320,9 @@ func TestAzureInstances_FilterExistingNodes(t *testing.T) {
|
||||
{
|
||||
name: "filter out matching node",
|
||||
instances: &AzureInstances{
|
||||
SubscriptionID: "sub-1",
|
||||
Metadata: AzureInstancesMetadata{
|
||||
SubscriptionID: "sub-1",
|
||||
},
|
||||
Instances: []*azure.VirtualMachine{
|
||||
{
|
||||
ID: "/subscriptions/sub-1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
||||
@@ -338,7 +342,9 @@ func TestAzureInstances_FilterExistingNodes(t *testing.T) {
|
||||
{
|
||||
name: "filter out all matching nodes",
|
||||
instances: &AzureInstances{
|
||||
SubscriptionID: "sub-1",
|
||||
Metadata: AzureInstancesMetadata{
|
||||
SubscriptionID: "sub-1",
|
||||
},
|
||||
Instances: []*azure.VirtualMachine{
|
||||
{
|
||||
ID: "/subscriptions/sub-1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
||||
@@ -359,7 +365,9 @@ func TestAzureInstances_FilterExistingNodes(t *testing.T) {
|
||||
{
|
||||
name: "different subscription is not filtered",
|
||||
instances: &AzureInstances{
|
||||
SubscriptionID: "sub-1",
|
||||
Metadata: AzureInstancesMetadata{
|
||||
SubscriptionID: "sub-1",
|
||||
},
|
||||
Instances: []*azure.VirtualMachine{
|
||||
{
|
||||
ID: "/subscriptions/sub-1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
||||
@@ -375,7 +383,9 @@ func TestAzureInstances_FilterExistingNodes(t *testing.T) {
|
||||
{
|
||||
name: "node without vm id is not used for filtering",
|
||||
instances: &AzureInstances{
|
||||
SubscriptionID: "sub-1",
|
||||
Metadata: AzureInstancesMetadata{
|
||||
SubscriptionID: "sub-1",
|
||||
},
|
||||
Instances: []*azure.VirtualMachine{
|
||||
{
|
||||
ID: "/subscriptions/sub-1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
||||
@@ -391,7 +401,9 @@ func TestAzureInstances_FilterExistingNodes(t *testing.T) {
|
||||
{
|
||||
name: "instance without properties is not filtered",
|
||||
instances: &AzureInstances{
|
||||
SubscriptionID: "sub-1",
|
||||
Metadata: AzureInstancesMetadata{
|
||||
SubscriptionID: "sub-1",
|
||||
},
|
||||
Instances: []*azure.VirtualMachine{
|
||||
{
|
||||
ID: "/subscriptions/sub-1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
||||
@@ -612,11 +624,13 @@ func TestMakeRunEvent(t *testing.T) {
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
instances := &AzureInstances{
|
||||
SubscriptionID: subscriptionID,
|
||||
ResourceGroup: resourceGroup,
|
||||
Region: region,
|
||||
Metadata: AzureInstancesMetadata{
|
||||
SubscriptionID: subscriptionID,
|
||||
ResourceGroup: resourceGroup,
|
||||
Region: region,
|
||||
},
|
||||
}
|
||||
evt := instances.MakeRunEvent(tc.result)
|
||||
evt := instances.Metadata.MakeRunEvent(tc.result)
|
||||
require.Equal(t, tc.want, evt)
|
||||
})
|
||||
}
|
||||
@@ -643,7 +657,9 @@ func TestMakeUsageEvent(t *testing.T) {
|
||||
{
|
||||
name: "node",
|
||||
instances: &AzureInstances{
|
||||
DiscoveryConfigName: discoveryConfig,
|
||||
Metadata: AzureInstancesMetadata{
|
||||
DiscoveryConfigName: discoveryConfig,
|
||||
},
|
||||
},
|
||||
wantKey: azureEventPrefix + resourceID,
|
||||
want: &usageeventsv1.ResourceCreateEvent{
|
||||
@@ -656,9 +672,11 @@ func TestMakeUsageEvent(t *testing.T) {
|
||||
{
|
||||
name: "agentless node",
|
||||
instances: &AzureInstances{
|
||||
DiscoveryConfigName: discoveryConfig,
|
||||
InstallerParams: &types.InstallerParams{
|
||||
ScriptName: installers.InstallerScriptNameAgentless,
|
||||
Metadata: AzureInstancesMetadata{
|
||||
DiscoveryConfigName: discoveryConfig,
|
||||
InstallerParams: &types.InstallerParams{
|
||||
ScriptName: installers.InstallerScriptNameAgentless,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantKey: azureEventPrefix + resourceID,
|
||||
@@ -673,7 +691,7 @@ func TestMakeUsageEvent(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
key, evt := tc.instances.MakeUsageEvent(vm)
|
||||
key, evt := tc.instances.Metadata.MakeUsageEvent(vm)
|
||||
require.Equal(t, tc.wantKey, key)
|
||||
require.Equal(t, tc.want, evt)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user