From 7195b10c8428a65f99974dfb786d5cae5fb06cc7 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Thu, 25 Jun 2026 09:14:13 +0200 Subject: [PATCH 01/23] feat(input.vsphere) : add custom properties --- plugins/inputs/vsphere/README.md | 56 +++- plugins/inputs/vsphere/endpoint.go | 359 +++++++++++++++++-------- plugins/inputs/vsphere/finder.go | 39 ++- plugins/inputs/vsphere/sample.conf | 22 +- plugins/inputs/vsphere/vsphere.go | 113 ++++---- plugins/inputs/vsphere/vsphere_test.go | 55 ++-- 6 files changed, 437 insertions(+), 207 deletions(-) diff --git a/plugins/inputs/vsphere/README.md b/plugins/inputs/vsphere/README.md index 295404064be23..1be2a87cd73ab 100644 --- a/plugins/inputs/vsphere/README.md +++ b/plugins/inputs/vsphere/README.md @@ -20,11 +20,11 @@ plugin ordering. See [CONFIGURATION.md][CONFIGURATION.md] for more details. [CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins -## Secret-store support +## Secret store support -This plugin supports secrets from secret-stores for the `username` and +This plugin supports secrets from secret stores for the `username` and `password` option. -See the [secret-store documentation][SECRETSTORE] for more details on how +See the [secret store documentation][SECRETSTORE] for more details on how to use them. [SECRETSTORE]: ../../../docs/CONFIGURATION.md#secret-store-secrets @@ -148,6 +148,9 @@ to use them. # cluster_metric_include = [] ## if omitted or empty, all metrics are collected # cluster_metric_exclude = [] ## Nothing excluded by default # cluster_instances = false ## false by default + ## Custom cluster properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # cluster_propertie_include = [] ## Resource Pools # resource_pool_include = [ "/*/host/**"] # Inventory path to resource pools to collect (by default all are collected) @@ -155,6 +158,9 @@ to use them. # resource_pool_metric_include = [] ## if omitted or empty, all metrics are collected # resource_pool_metric_exclude = [] ## Nothing excluded by default # resource_pool_instances = false ## false by default + ## Custom resource_pool properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # resource_pool_propertie_include = [] ## Datastores # datastore_include = [ "/*/datastore/**"] # Inventory path to datastores to collect (by default all are collected) @@ -162,6 +168,9 @@ to use them. # datastore_metric_include = [] ## if omitted or empty, all metrics are collected # datastore_metric_exclude = [] ## Nothing excluded by default # datastore_instances = false ## false by default + ## Custom datastore properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # datastore_propertie_include = [] ## Datacenters # datacenter_include = [ "/*/host/**"] # Inventory path to clusters to collect (by default all are collected) @@ -169,12 +178,18 @@ to use them. datacenter_metric_include = [] ## if omitted or empty, all metrics are collected datacenter_metric_exclude = [ "*" ] ## Datacenters are not collected by default. # datacenter_instances = false ## false by default + ## Custom cluster properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # datacenter_propertie_include = [] ## VSAN # vsan_metric_include = [] ## if omitted or empty, all metrics are collected # vsan_metric_exclude = [ "*" ] ## vSAN are not collected by default. ## Whether to skip verifying vSAN metrics against the ones from GetSupportedEntityTypes API. # vsan_metric_skip_verify = false ## false by default. + ## Custom vsan properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # vsan_propertie_include = [] ## Interval for sampling vSAN performance metrics, can be reduced down to ## 30 seconds for vSAN 8 U1. @@ -952,6 +967,41 @@ disk.capacity.usage.average * virtualDisk stats for VM * disk (name of virtual disk) +#### Collect custom tag + +**_propertie_include allow you to collect data that are not metrics. + +For sample : + +```toml + host_propertie_include = ["summary.runtime.powerState", "summary.overallStatus", "summary.runtime.inMaintenanceMode", "summary.runtime.connectionState"] + vm_propertie_include = ["runtime.powerState"] + cluster_propertie_include = ["summary.overallStatus"] + datastore_propertie_include = ["summary.accessible"] + resource_pool_propertie_include = ["overallStatus"] +``` + +All that tag will be added to "internal" metric that always contain "1". +It is recommanded to use it with processors.enum to convert as metric. + +For sample : + +For sample : + +```toml +[[processors.enum]] + [[processors.enum.mapping]] + ## Names of the fields to map. Globs accepted. + tag = "powerstate" + default = 0 + + ## Table of mappings + [processors.enum.mapping.value_mappings] + poweredOn = 1 + suspended = 2 + poweredOff = 3 +``` + ## Add a vSAN extension A vSAN resource is a special type of resource that can be collected by the diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index 8c03c7c389917..a2c84dd77f9d1 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -7,12 +7,14 @@ import ( "math" "math/rand" "net/url" + "reflect" "regexp" "strconv" "strings" "sync" "sync/atomic" "time" + "unicode" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/performance" @@ -75,13 +77,14 @@ type resourceKind struct { paths []string excludePaths []string collectInstances bool - getObjects func(context.Context, *endpoint, *resourceFilter) (objectMap, error) + getObjects func(context.Context, *endpoint, *resourceFilter, []string) (objectMap, error) include []string simple bool metrics performance.MetricList parent string latestSample time.Time lastColl time.Time + PropertieInclude []string } type metricEntry struct { @@ -94,17 +97,16 @@ type metricEntry struct { type objectMap map[string]*objectRef type objectRef struct { - name string - altID string - ref types.ManagedObjectReference - parentRef *types.ManagedObjectReference // Pointer because it must be nillable - guest string - memorySizeMB int32 - memoryReservation int32 - dcname string - rpname string - customValues map[string]string - lookup map[string]string + name string + altID string + ref types.ManagedObjectReference + parentRef *types.ManagedObjectReference // Pointer because it must be nillable + guest string + dcname string + rpname string + customValues map[string]string + customProperties map[string]interface{} + lookup map[string]string } func (e *endpoint) getParent(obj *objectRef, res *resourceKind) (*objectRef, bool) { @@ -149,6 +151,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.DatacenterInstances, getObjects: getDatacenters, parent: "", + PropertieInclude: parent.DatacenterPropertieInclude, }, "cluster": { name: "cluster", @@ -167,6 +170,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.ClusterInstances, getObjects: getClusters, parent: "datacenter", + PropertieInclude: parent.ClusterPropertieInclude, }, "resourcepool": { name: "resourcepool", @@ -185,6 +189,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.ResourcePoolInstances, getObjects: getResourcePools, parent: "cluster", + PropertieInclude: parent.ResourcePoolPropertieInclude, }, "host": { name: "host", @@ -203,6 +208,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.HostInstances, getObjects: getHosts, parent: "cluster", + PropertieInclude: parent.HostPropertieInclude, }, "vm": { name: "vm", @@ -221,6 +227,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.VMInstances, getObjects: getVMs, parent: "host", + PropertieInclude: parent.VMPropertieInclude, }, "datastore": { name: "datastore", @@ -238,6 +245,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.DatastoreInstances, getObjects: getDatastores, parent: "", + PropertieInclude: parent.DatastorePropertieInclude, }, "vsan": { name: "vsan", @@ -255,6 +263,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: false, getObjects: getClusters, parent: "datacenter", + PropertieInclude: parent.VSANPropertieInclude, }, } @@ -482,10 +491,11 @@ func (e *endpoint) discover(ctx context.Context) error { finder: &finder{client}, resType: res.vcName, paths: res.paths, - excludePaths: res.excludePaths} + excludePaths: res.excludePaths, + custoFields: res.PropertieInclude} ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) - objects, err := res.getObjects(ctx1, e, &rf) + objects, err := res.getObjects(ctx1, e, &rf, res.PropertieInclude) cancel1() if err != nil { return err @@ -637,7 +647,7 @@ func (e *endpoint) complexMetadataSelect(ctx context.Context, res *resourceKind, te.wait() } -func getDatacenters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter) (objectMap, error) { +func getDatacenters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { var resources []mo.Datacenter ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() @@ -650,17 +660,18 @@ func getDatacenters(ctx context.Context, e *endpoint, resourceFilter *resourceFi r := &resources[i] m[r.ExtensibleManagedObject.Reference().Value] = &objectRef{ - name: r.Name, - ref: r.ExtensibleManagedObject.Reference(), - parentRef: r.Parent, - dcname: r.Name, - customValues: e.loadCustomAttributes(r.ManagedEntity), + name: r.Name, + ref: r.ExtensibleManagedObject.Reference(), + parentRef: r.Parent, + dcname: r.Name, + customValues: e.loadCustomAttributes(r.ManagedEntity), + customProperties: e.loadCustomProperties(r, PropertieInclude), } } return m, nil } -func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter) (objectMap, error) { +func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { var resources []mo.ClusterComputeResource ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() @@ -699,10 +710,11 @@ func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilte } } m[r.ExtensibleManagedObject.Reference().Value] = &objectRef{ - name: r.Name, - ref: r.ExtensibleManagedObject.Reference(), - parentRef: p, - customValues: e.loadCustomAttributes(r.ManagedEntity), + name: r.Name, + ref: r.ExtensibleManagedObject.Reference(), + parentRef: p, + customValues: e.loadCustomAttributes(r.ManagedEntity), + customProperties: e.loadCustomProperties(r, PropertieInclude), } return nil }() @@ -714,7 +726,7 @@ func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilte } // noinspection GoUnusedParameter -func getResourcePools(ctx context.Context, e *endpoint, resourceFilter *resourceFilter) (objectMap, error) { +func getResourcePools(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { var resources []mo.ResourcePool err := resourceFilter.findAll(ctx, &resources) if err != nil { @@ -725,10 +737,11 @@ func getResourcePools(ctx context.Context, e *endpoint, resourceFilter *resource r := &resources[i] m[r.ExtensibleManagedObject.Reference().Value] = &objectRef{ - name: r.Name, - ref: r.ExtensibleManagedObject.Reference(), - parentRef: r.Parent, - customValues: e.loadCustomAttributes(r.ManagedEntity), + name: r.Name, + ref: r.ExtensibleManagedObject.Reference(), + parentRef: r.Parent, + customValues: e.loadCustomAttributes(r.ManagedEntity), + customProperties: e.loadCustomProperties(r, PropertieInclude), } } return m, nil @@ -745,7 +758,7 @@ func getResourcePoolName(rp types.ManagedObjectReference, rps objectMap) string } // noinspection GoUnusedParameter -func getHosts(ctx context.Context, e *endpoint, resourceFilter *resourceFilter) (objectMap, error) { +func getHosts(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { var resources []mo.HostSystem err := resourceFilter.findAll(ctx, &resources) if err != nil { @@ -755,17 +768,21 @@ func getHosts(ctx context.Context, e *endpoint, resourceFilter *resourceFilter) for i := range resources { r := &resources[i] + lookup := make(map[string]string) + m[r.ExtensibleManagedObject.Reference().Value] = &objectRef{ - name: r.Name, - ref: r.ExtensibleManagedObject.Reference(), - parentRef: r.Parent, - customValues: e.loadCustomAttributes(r.ManagedEntity), + name: r.Name, + ref: r.ExtensibleManagedObject.Reference(), + parentRef: r.Parent, + customValues: e.loadCustomAttributes(r.ManagedEntity), + customProperties: e.loadCustomProperties(r, PropertieInclude), + lookup: lookup, } } return m, nil } -func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter) (objectMap, error) { +func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter, PropertieInclude []string) (objectMap, error) { var resources []mo.VirtualMachine ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() @@ -784,27 +801,25 @@ func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter) (objectMap, er resType: "ResourcePool", paths: []string{"/*/host/**"}, excludePaths: nil} - resourcePools, err := getResourcePools(ctx, e, &rprf) + resourcePools, err := getResourcePools(ctx, e, &rprf, []string{}) if err != nil { return nil, err } for i := range resources { r := &resources[i] - - if r.Runtime.PowerState != "poweredOn" { - continue - } + rpname := "" guest := "unknown" uuid := "" lookup := make(map[string]string) - // get the name of the VM resource pool - rpname := getResourcePoolName(*r.ResourcePool, resourcePools) + if r.Runtime.PowerState == "poweredOn" { + // get the name of the VM resource pool + rpname = getResourcePoolName(*r.ResourcePool, resourcePools) + } // Extract host name if r.Guest != nil && r.Guest.HostName != "" { lookup["guesthostname"] = r.Guest.HostName } - // Collect network information for _, net := range r.Guest.Net { if net.DeviceConfigId == -1 { @@ -847,40 +862,22 @@ func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter) (objectMap, er uuid = r.Config.Uuid } - cvs := make(map[string]string) - if e.customAttrEnabled { - for _, cv := range r.Summary.CustomValue { - val := cv.(*types.CustomFieldStringValue) - if val.Value == "" { - continue - } - key, ok := e.customFields[val.Key] - if !ok { - e.log.Warnf("Metadata for custom field %d not found. Skipping", val.Key) - continue - } - if e.customAttrFilter.Match(key) { - cvs[key] = val.Value - } - } - } m[r.ExtensibleManagedObject.Reference().Value] = &objectRef{ - name: r.Name, - ref: r.ExtensibleManagedObject.Reference(), - parentRef: r.Runtime.Host, - guest: guest, - memorySizeMB: r.Summary.Config.MemorySizeMB, - memoryReservation: r.Summary.Config.MemoryReservation, - altID: uuid, - rpname: rpname, - customValues: e.loadCustomAttributes(r.ManagedEntity), - lookup: lookup, + name: r.Name, + ref: r.ExtensibleManagedObject.Reference(), + parentRef: r.Runtime.Host, + guest: guest, + altID: uuid, + rpname: rpname, + customValues: e.loadCustomAttributes(r.ManagedEntity), + customProperties: e.loadCustomProperties(r, PropertieInclude), + lookup: lookup, } } return m, nil } -func getDatastores(ctx context.Context, e *endpoint, resourceFilter *resourceFilter) (objectMap, error) { +func getDatastores(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { var resources []mo.Datastore ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() @@ -900,34 +897,48 @@ func getDatastores(ctx context.Context, e *endpoint, resourceFilter *resourceFil } } m[r.ExtensibleManagedObject.Reference().Value] = &objectRef{ - name: r.Name, - ref: r.ExtensibleManagedObject.Reference(), - parentRef: r.Parent, - altID: lunID, - customValues: e.loadCustomAttributes(r.ManagedEntity), + name: r.Name, + ref: r.ExtensibleManagedObject.Reference(), + parentRef: r.Parent, + altID: lunID, + customValues: e.loadCustomAttributes(r.ManagedEntity), + customProperties: e.loadCustomProperties(r, PropertieInclude), } } return m, nil } func (e *endpoint) loadCustomAttributes(entity mo.ManagedEntity) map[string]string { - if !e.customAttrEnabled { - return make(map[string]string) - } cvs := make(map[string]string) - for _, v := range entity.CustomValue { - cv, ok := v.(*types.CustomFieldStringValue) - if !ok { - e.parent.Log.Warnf("Metadata for custom field %d not of string type. Skipping", cv.Key) - continue - } - key, ok := e.customFields[cv.Key] - if !ok { - e.parent.Log.Warnf("Metadata for custom field %d not found. Skipping", cv.Key) - continue + if e.customAttrEnabled { + for _, v := range entity.CustomValue { + cv, ok := v.(*types.CustomFieldStringValue) + if !ok { + e.parent.Log.Warnf("Metadata for custom field %d not of string type. Skipping", cv.Key) + continue + } + key, ok := e.customFields[cv.Key] + if !ok { + e.parent.Log.Warnf("Metadata for custom field %d not found. Skipping", cv.Key) + continue + } + if e.customAttrFilter.Match(key) { + cvs[key] = cv.Value + } } - if e.customAttrFilter.Match(key) { - cvs[key] = cv.Value + } + + return cvs +} + +func (e *endpoint) loadCustomProperties(entity interface{}, PropertieInclude []string) map[string]interface{} { + cvs := make(map[string]interface{}) + if len(PropertieInclude) != 0 { + for _, filtre := range PropertieInclude { + if valeur, ok := e.getExtraData(entity, capitalizeAfterDot(filtre)); ok { + key := e.makePropertyIdentifier(filtre) + cvs[key] = valeur + } } } return cvs @@ -1272,7 +1283,26 @@ func (e *endpoint) collectChunk( nValues := 0 alignedInfo, alignedValues := e.alignSamples(em.SampleInfo, v.Value, interval) - globalFields := e.populateGlobalFields(objectRef, resourceType, prefix) + globalFields := e.populateGlobalFields(objectRef) + + if len(globalFields) != 0 { + mn, fn := e.makeMetricIdentifier(prefix, "internal") + bKey := mn + " " + v.Instance + " " + strconv.FormatInt(latestSample.UnixNano(), 10) + bucket, found := buckets[bKey] + if !found { + fields := make(map[string]interface{}) + fields[fn] = int64(1.0) + tags := map[string]string{} + for k, v := range t { + tags[k] = v + } + for k, v := range globalFields { + tags[k] = fmt.Sprintf("%v", v) + } + bucket = metricEntry{name: mn, ts: latestSample, fields: fields, tags: tags} + buckets[bKey] = bucket + } + } for idx, sample := range alignedInfo { // According to the docs, SampleInfo and Value should have the same length, but we've seen corrupted @@ -1293,9 +1323,6 @@ func (e *endpoint) collectChunk( bucket, found := buckets[bKey] if !found { fields := make(map[string]interface{}) - for k, v := range globalFields { - fields[k] = v - } bucket = metricEntry{name: mn, ts: ts, fields: fields, tags: t} buckets[bKey] = bucket } @@ -1326,6 +1353,7 @@ func (e *endpoint) collectChunk( continue } } + // We've iterated through all the metrics and collected buckets for each // measurement name. Now emit them! for _, bucket := range buckets { @@ -1353,17 +1381,32 @@ func (e *endpoint) populateTags(objectRef *objectRef, resourceType string, resou if found { t[resource.parentTag] = parent.name if resourceType == "vm" { - if objectRef.guest != "" { - t["guest"] = objectRef.guest - } - if gh := objectRef.lookup["guesthostname"]; gh != "" { - t["guesthostname"] = gh - } if c, ok := e.resourceKinds["cluster"].objects[parent.parentRef.Value]; ok { t["clustername"] = c.name } } } + if resourceType == "vm" { + if objectRef.guest != "" { + t["guest"] = objectRef.guest + } + // if objectRef.lookup["powerstate"] != "" { + // t["powerstate"] = objectRef.lookup["powerstate"] + // } + if gh := objectRef.lookup["guesthostname"]; gh != "" { + t["guesthostname"] = gh + } + if c, ok := e.resourceKinds["cluster"].objects[parent.parentRef.Value]; ok { + t["clustername"] = c.name + } + } else if resourceType == "host" { + // if objectRef.lookup["powerstate"] != "" { + // t["powerstate"] = objectRef.lookup["powerstate"] + // } + // if objectRef.lookup["maintenance"] != "" { + // t["maintenance"] = objectRef.lookup["maintenance"] + // } + } // Fill in Datacenter name if objectRef.dcname != "" { @@ -1423,20 +1466,15 @@ func (e *endpoint) populateTags(objectRef *objectRef, resourceType string, resou } } -func (e *endpoint) populateGlobalFields(objectRef *objectRef, resourceType, prefix string) map[string]interface{} { +func (e *endpoint) populateGlobalFields(objectRef *objectRef) map[string]interface{} { globalFields := make(map[string]interface{}) - if resourceType == "vm" && objectRef.memorySizeMB != 0 { - _, fieldName := e.makeMetricIdentifier(prefix, "memorySizeMB") - globalFields[fieldName] = strconv.Itoa(int(objectRef.memorySizeMB)) - } - if resourceType == "vm" && objectRef.memoryReservation != 0 { - _, fieldName := e.makeMetricIdentifier(prefix, "memoryReservation") - globalFields[fieldName] = strconv.Itoa(int(objectRef.memoryReservation)) + for k, v := range objectRef.customProperties { + globalFields[k] = v } return globalFields } -func (e *endpoint) makeMetricIdentifier(prefix, metric string) (metricName, fieldName string) { +func (e *endpoint) makeMetricIdentifier(prefix string, metric string) (metricName string, fieldName string) { parts := strings.Split(metric, ".") if len(parts) == 1 { return prefix, parts[0] @@ -1460,3 +1498,102 @@ func round(x float64) float64 { } return t } + +func getIndex(enumObj interface{}) int { + v := reflect.ValueOf(enumObj) + + // Vérifier si le type a une méthode Values() + method := v.MethodByName("Values") + if !method.IsValid() { + // La méthode n'existe pas + return -1 + } + for i, s := range method.Call(nil) { + if s == enumObj { + return i + } + } + return -1 // si non trouvé +} + +func (e *endpoint) getExtraData(entity interface{}, fieldPath string) (interface{}, bool) { + v := reflect.ValueOf(entity) + + // Si c'est un pointeur, on dé-référence + if v.Kind() == reflect.Ptr { + if v.IsNil() { + // La valeur est un pointeur nil, on ne peut pas continuer + return nil, false + } + v = v.Elem() + } + + fields := strings.Split(fieldPath, ".") + for _, field := range fields { + if v.Kind() == reflect.Struct { + v = v.FieldByName(field) + // Si le champ n'existe pas ou n'est pas accessible + if !v.IsValid() { + e.parent.Log.Warnf("Field %s in %s of %s not valid. Skipping", field, fieldPath, reflect.TypeOf(entity)) + return nil, false + } + // Si c'est un pointeur, dé-référencer + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + } else { + e.parent.Log.Warnf("Field %s in %s of %s not struct %s. Skipping", field, fieldPath, reflect.TypeOf(entity), v.Kind()) + return nil, false + } + } + + // Retourner la valeur sous forme de string + if v.IsValid() && v.CanInterface() { + return v, true + } + e.parent.Log.Warnf("Field %s of %s no interface. Skipping", fieldPath, reflect.TypeOf(entity)) + return nil, false +} + +func (e *endpoint) printStructFields(t reflect.Type, prefix string) { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() != reflect.Struct { + e.parent.Log.Warnf("Not a struct:", t) + return + } + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + e.parent.Log.Warnf(prefix, "Field:", field.Name, "Type:", field.Type) + // Si vous voulez explorer récursivement + if field.Type.Kind() == reflect.Struct { + e.printStructFields(field.Type, prefix+">") + } + } +} + +func (e *endpoint) makePropertyIdentifier(input string) string { + var result []rune + runes := []rune(e.parent.Separator) + for _, r := range input { + if r == '.' { + result = append(result, runes...) + } else { + result = append(result, r) + } + } + return string(result) +} + +func capitalizeAfterDot(input string) string { + var result []rune + for i, r := range input { + if i == 0 || input[i-1] == '.' { + result = append(result, unicode.ToUpper(r)) + } else { + result = append(result, r) + } + } + return string(result) +} diff --git a/plugins/inputs/vsphere/finder.go b/plugins/inputs/vsphere/finder.go index 378c09dbc3738..d18012addc2ee 100644 --- a/plugins/inputs/vsphere/finder.go +++ b/plugins/inputs/vsphere/finder.go @@ -29,20 +29,21 @@ type resourceFilter struct { resType string paths []string excludePaths []string + custoFields []string } // findAll returns the union of resources found given the supplied resource type and paths. -func (f *finder) findAll(ctx context.Context, resType string, paths, excludePaths []string, dst interface{}) error { +func (f *finder) findAll(ctx context.Context, resType string, paths, excludePaths []string, dst interface{}, custoFields []string) error { objs := make(map[string]types.ObjectContent) for _, p := range paths { - if err := f.findResources(ctx, resType, p, objs); err != nil { + if err := f.findResources(ctx, resType, p, objs, custoFields); err != nil { return err } } if len(excludePaths) > 0 { excludes := make(map[string]types.ObjectContent) for _, p := range excludePaths { - if err := f.findResources(ctx, resType, p, excludes); err != nil { + if err := f.findResources(ctx, resType, p, excludes, custoFields); err != nil { return err } } @@ -54,22 +55,22 @@ func (f *finder) findAll(ctx context.Context, resType string, paths, excludePath } // find returns the resources matching the specified path. -func (f *finder) find(ctx context.Context, resType, path string, dst interface{}) error { +func (f *finder) find(ctx context.Context, resType, path string, dst interface{}, custoFields []string) error { objs := make(map[string]types.ObjectContent) - err := f.findResources(ctx, resType, path, objs) + err := f.findResources(ctx, resType, path, objs, custoFields) if err != nil { return err } return objectContentToTypedArray(objs, dst) } -func (f *finder) findResources(ctx context.Context, resType, path string, objs map[string]types.ObjectContent) error { +func (f *finder) findResources(ctx context.Context, resType, path string, objs map[string]types.ObjectContent, custoFields []string) error { p := strings.Split(path, "/") flt := make([]property.Match, len(p)-1) for i := 1; i < len(p); i++ { flt[i-1] = property.Match{"name": p[i]} } - err := f.descend(ctx, f.client.client.ServiceContent.RootFolder, resType, flt, 0, objs) + err := f.descend(ctx, f.client.client.ServiceContent.RootFolder, resType, flt, 0, objs, custoFields) if err != nil { return err } @@ -78,7 +79,7 @@ func (f *finder) findResources(ctx context.Context, resType, path string, objs m } func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, resType string, - tokens []property.Match, pos int, objs map[string]types.ObjectContent) error { + tokens []property.Match, pos int, objs map[string]types.ObjectContent, custoFields []string) error { isLeaf := pos == len(tokens)-1 // No more tokens to match? @@ -110,6 +111,8 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, if af, ok := addFields[resType]; ok { fields = append(fields, af...) } + fields = append(fields, custoFields...) + uniqueFields := removeDuplicates(fields) if recurse { // Special case: The last token is a recursive wildcard, so we can grab everything // recursively in a single call. @@ -118,7 +121,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, return err } defer v2.Destroy(ctx) //nolint:errcheck // Ignore the returned error as we cannot do anything about it anyway - err = v2.Retrieve(ctx, []string{resType}, fields, &content) + err = v2.Retrieve(ctx, []string{resType}, uniqueFields, &content) if err != nil { return err } @@ -175,7 +178,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, // The normal case: Advance to next token before descending inc = 1 } - err := f.descend(ctx, c.Obj, resType, tokens, pos+inc, objs) + err := f.descend(ctx, c.Obj, resType, tokens, pos+inc, objs, custoFields) if err != nil { return err } @@ -185,7 +188,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, // We're at a "pseudo leaf", i.e. we looked ahead a token and found that this level contains leaf nodes. // Rerun the entire level as a leaf to get those nodes. This will only be executed when pos is one token // before the last, to pos+1 will always point to a leaf token. - return f.descend(ctx, root, resType, tokens, pos+1, objs) + return f.descend(ctx, root, resType, tokens, pos+1, objs, custoFields) } return nil @@ -224,7 +227,7 @@ func objectContentToTypedArray(objs map[string]types.ObjectContent, dst interfac // findAll finds all resources matching the paths that were specified upon creation of the resourceFilter. func (r *resourceFilter) findAll(ctx context.Context, dst interface{}) error { - return r.finder.findAll(ctx, r.resType, r.paths, r.excludePaths, dst) + return r.finder.findAll(ctx, r.resType, r.paths, r.excludePaths, dst, r.custoFields) } func matchName(f property.Match, props []types.DynamicProperty) bool { @@ -274,3 +277,15 @@ func init() { "VirtualApp": nil, } } + +func removeDuplicates(input []string) []string { + seen := make(map[string]struct{}) + var result []string + for _, v := range input { + if _, exists := seen[v]; !exists { + seen[v] = struct{}{} + result = append(result, v) + } + } + return result +} diff --git a/plugins/inputs/vsphere/sample.conf b/plugins/inputs/vsphere/sample.conf index 6b865cf2434a8..6f805111385e9 100644 --- a/plugins/inputs/vsphere/sample.conf +++ b/plugins/inputs/vsphere/sample.conf @@ -48,6 +48,9 @@ ] # vm_metric_exclude = [] ## Nothing is excluded by default # vm_instances = true ## true by default + ## Custom VM properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # vm_propertie_include = ["runtime.powerState", "summary.config.MemorySizeMB", "summary.config.MemoryReservation"] ## Hosts ## Typical host metrics (if omitted or empty, all metrics are collected) @@ -106,7 +109,9 @@ # host_metric_exclude = [] ## Nothing excluded by default # host_instances = true ## true by default - + ## Custom host properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # host_propertie_include = ["summary.runtime.powerState", "summary.overallStatus", "summary.runtime.inMaintenanceMode", "summary.runtime.connectionState"] ## Clusters # cluster_include = [ "/*/host/**"] # Inventory path to clusters to collect (by default all are collected) @@ -114,6 +119,9 @@ # cluster_metric_include = [] ## if omitted or empty, all metrics are collected # cluster_metric_exclude = [] ## Nothing excluded by default # cluster_instances = false ## false by default + ## Custom cluster properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # cluster_propertie_include = [] ## Resource Pools # resource_pool_include = [ "/*/host/**"] # Inventory path to resource pools to collect (by default all are collected) @@ -121,6 +129,9 @@ # resource_pool_metric_include = [] ## if omitted or empty, all metrics are collected # resource_pool_metric_exclude = [] ## Nothing excluded by default # resource_pool_instances = false ## false by default + ## Custom resource_pool properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # resource_pool_propertie_include = [] ## Datastores # datastore_include = [ "/*/datastore/**"] # Inventory path to datastores to collect (by default all are collected) @@ -128,6 +139,9 @@ # datastore_metric_include = [] ## if omitted or empty, all metrics are collected # datastore_metric_exclude = [] ## Nothing excluded by default # datastore_instances = false ## false by default + ## Custom datastore properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # datastore_propertie_include = [] ## Datacenters # datacenter_include = [ "/*/host/**"] # Inventory path to clusters to collect (by default all are collected) @@ -135,12 +149,18 @@ datacenter_metric_include = [] ## if omitted or empty, all metrics are collected datacenter_metric_exclude = [ "*" ] ## Datacenters are not collected by default. # datacenter_instances = false ## false by default + ## Custom cluster properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # datacenter_propertie_include = [] ## VSAN # vsan_metric_include = [] ## if omitted or empty, all metrics are collected # vsan_metric_exclude = [ "*" ] ## vSAN are not collected by default. ## Whether to skip verifying vSAN metrics against the ones from GetSupportedEntityTypes API. # vsan_metric_skip_verify = false ## false by default. + ## Custom vsan properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # vsan_propertie_include = [] ## Interval for sampling vSAN performance metrics, can be reduced down to ## 30 seconds for vSAN 8 U1. diff --git a/plugins/inputs/vsphere/vsphere.go b/plugins/inputs/vsphere/vsphere.go index c1060073ca501..c33f814344ee0 100644 --- a/plugins/inputs/vsphere/vsphere.go +++ b/plugins/inputs/vsphere/vsphere.go @@ -21,59 +21,66 @@ import ( var sampleConfig string type VSphere struct { - Vcenters []string `toml:"vcenters"` - Username config.Secret `toml:"username"` - Password config.Secret `toml:"password"` - DatacenterInstances bool `toml:"datacenter_instances"` - DatacenterMetricInclude []string `toml:"datacenter_metric_include"` - DatacenterMetricExclude []string `toml:"datacenter_metric_exclude"` - DatacenterInclude []string `toml:"datacenter_include"` - DatacenterExclude []string `toml:"datacenter_exclude"` - ClusterInstances bool `toml:"cluster_instances"` - ClusterMetricInclude []string `toml:"cluster_metric_include"` - ClusterMetricExclude []string `toml:"cluster_metric_exclude"` - ClusterInclude []string `toml:"cluster_include"` - ClusterExclude []string `toml:"cluster_exclude"` - ResourcePoolInstances bool `toml:"resource_pool_instances"` - ResourcePoolMetricInclude []string `toml:"resource_pool_metric_include"` - ResourcePoolMetricExclude []string `toml:"resource_pool_metric_exclude"` - ResourcePoolInclude []string `toml:"resource_pool_include"` - ResourcePoolExclude []string `toml:"resource_pool_exclude"` - HostInstances bool `toml:"host_instances"` - HostMetricInclude []string `toml:"host_metric_include"` - HostMetricExclude []string `toml:"host_metric_exclude"` - HostInclude []string `toml:"host_include"` - HostExclude []string `toml:"host_exclude"` - VMInstances bool `toml:"vm_instances"` - VMMetricInclude []string `toml:"vm_metric_include"` - VMMetricExclude []string `toml:"vm_metric_exclude"` - VMInclude []string `toml:"vm_include"` - VMExclude []string `toml:"vm_exclude"` - DatastoreInstances bool `toml:"datastore_instances"` - DatastoreMetricInclude []string `toml:"datastore_metric_include"` - DatastoreMetricExclude []string `toml:"datastore_metric_exclude"` - DatastoreInclude []string `toml:"datastore_include"` - DatastoreExclude []string `toml:"datastore_exclude"` - VSANMetricInclude []string `toml:"vsan_metric_include"` - VSANMetricExclude []string `toml:"vsan_metric_exclude"` - VSANMetricSkipVerify bool `toml:"vsan_metric_skip_verify"` - VSANClusterInclude []string `toml:"vsan_cluster_include"` - VSANInterval config.Duration `toml:"vsan_interval"` - Separator string `toml:"separator"` - CustomAttributeInclude []string `toml:"custom_attribute_include"` - CustomAttributeExclude []string `toml:"custom_attribute_exclude"` - UseIntSamples bool `toml:"use_int_samples"` - IPAddresses []string `toml:"ip_addresses"` - MetricLookback int `toml:"metric_lookback"` - DisconnectedServersBehavior string `toml:"disconnected_servers_behavior"` - MaxQueryObjects int `toml:"max_query_objects"` - MaxQueryMetrics int `toml:"max_query_metrics"` - CollectConcurrency int `toml:"collect_concurrency"` - DiscoverConcurrency int `toml:"discover_concurrency"` - ObjectDiscoveryInterval config.Duration `toml:"object_discovery_interval"` - Timeout config.Duration `toml:"timeout"` - HistoricalInterval config.Duration `toml:"historical_interval"` - Log telegraf.Logger `toml:"-"` + Vcenters []string `toml:"vcenters"` + Username config.Secret `toml:"username"` + Password config.Secret `toml:"password"` + DatacenterInstances bool `toml:"datacenter_instances"` + DatacenterMetricInclude []string `toml:"datacenter_metric_include"` + DatacenterMetricExclude []string `toml:"datacenter_metric_exclude"` + DatacenterInclude []string `toml:"datacenter_include"` + DatacenterExclude []string `toml:"datacenter_exclude"` + DatacenterPropertieInclude []string `toml:"datacenter_propertie_include"` + ClusterInstances bool `toml:"cluster_instances"` + ClusterMetricInclude []string `toml:"cluster_metric_include"` + ClusterMetricExclude []string `toml:"cluster_metric_exclude"` + ClusterInclude []string `toml:"cluster_include"` + ClusterExclude []string `toml:"cluster_exclude"` + ClusterPropertieInclude []string `toml:"cluster_propertie_include"` + ResourcePoolInstances bool `toml:"resource_pool_instances"` + ResourcePoolMetricInclude []string `toml:"resource_pool_metric_include"` + ResourcePoolMetricExclude []string `toml:"resource_pool_metric_exclude"` + ResourcePoolInclude []string `toml:"resource_pool_include"` + ResourcePoolExclude []string `toml:"resource_pool_exclude"` + ResourcePoolPropertieInclude []string `toml:"resource_pool_propertie_include"` + HostInstances bool `toml:"host_instances"` + HostMetricInclude []string `toml:"host_metric_include"` + HostMetricExclude []string `toml:"host_metric_exclude"` + HostInclude []string `toml:"host_include"` + HostExclude []string `toml:"host_exclude"` + HostPropertieInclude []string `toml:"host_propertie_include"` + VMInstances bool `toml:"vm_instances"` + VMMetricInclude []string `toml:"vm_metric_include"` + VMMetricExclude []string `toml:"vm_metric_exclude"` + VMInclude []string `toml:"vm_include"` + VMExclude []string `toml:"vm_exclude"` + VMPropertieInclude []string `toml:"vm_propertie_include"` + DatastoreInstances bool `toml:"datastore_instances"` + DatastoreMetricInclude []string `toml:"datastore_metric_include"` + DatastoreMetricExclude []string `toml:"datastore_metric_exclude"` + DatastoreInclude []string `toml:"datastore_include"` + DatastoreExclude []string `toml:"datastore_exclude"` + DatastorePropertieInclude []string `toml:"datastore_propertie_include"` + VSANMetricInclude []string `toml:"vsan_metric_include"` + VSANMetricExclude []string `toml:"vsan_metric_exclude"` + VSANMetricSkipVerify bool `toml:"vsan_metric_skip_verify"` + VSANClusterInclude []string `toml:"vsan_cluster_include"` + VSANInterval config.Duration `toml:"vsan_interval"` + VSANPropertieInclude []string `toml:"vsan_propertie_include"` + Separator string `toml:"separator"` + CustomAttributeInclude []string `toml:"custom_attribute_include"` + CustomAttributeExclude []string `toml:"custom_attribute_exclude"` + UseIntSamples bool `toml:"use_int_samples"` + IPAddresses []string `toml:"ip_addresses"` + MetricLookback int `toml:"metric_lookback"` + DisconnectedServersBehavior string `toml:"disconnected_servers_behavior"` + MaxQueryObjects int `toml:"max_query_objects"` + MaxQueryMetrics int `toml:"max_query_metrics"` + CollectConcurrency int `toml:"collect_concurrency"` + DiscoverConcurrency int `toml:"discover_concurrency"` + ObjectDiscoveryInterval config.Duration `toml:"object_discovery_interval"` + Timeout config.Duration `toml:"timeout"` + HistoricalInterval config.Duration `toml:"historical_interval"` + Log telegraf.Logger `toml:"-"` tls.ClientConfig // Mix in the TLS/SSL goodness from core proxy.HTTPProxy diff --git a/plugins/inputs/vsphere/vsphere_test.go b/plugins/inputs/vsphere/vsphere_test.go index 9cba70c720af5..f20e0da3bfd8c 100644 --- a/plugins/inputs/vsphere/vsphere_test.go +++ b/plugins/inputs/vsphere/vsphere_test.go @@ -242,10 +242,10 @@ func TestMaxQuery(t *testing.T) { c2.close() } -func testLookupVM(ctx context.Context, t *testing.T, f *finder, path string, expected int, expectedName string) { +func testLookupVM(ctx context.Context, t *testing.T, f *finder, path string, expected int, expectedName string, custoFields []string) { poweredOn := types.VirtualMachinePowerState("poweredOn") var vm []mo.VirtualMachine - err := f.find(ctx, "VirtualMachine", path, &vm) + err := f.find(ctx, "VirtualMachine", path, &vm, custoFields) require.NoError(t, err) require.Len(t, vm, expected) if expectedName != "" { @@ -274,50 +274,51 @@ func TestFinder(t *testing.T) { f := finder{c} var dc []mo.Datacenter - err = f.find(t.Context(), "Datacenter", "/DC0", &dc) + err = f.find(t.Context(), "Datacenter", "/DC0", &dc, []string{}) require.NoError(t, err) require.Len(t, dc, 1) require.Equal(t, "DC0", dc[0].Name) var host []mo.HostSystem - err = f.find(t.Context(), "HostSystem", "/DC0/host/DC0_H0/DC0_H0", &host) + err = f.find(t.Context(), "HostSystem", "/DC0/host/DC0_H0/DC0_H0", &host, []string{}) require.NoError(t, err) require.Len(t, host, 1) require.Equal(t, "DC0_H0", host[0].Name) host = make([]mo.HostSystem, 0) - err = f.find(t.Context(), "HostSystem", "/DC0/host/DC0_C0/DC0_C0_H0", &host) + err = f.find(t.Context(), "HostSystem", "/DC0/host/DC0_C0/DC0_C0_H0", &host, []string{}) require.NoError(t, err) require.Len(t, host, 1) require.Equal(t, "DC0_C0_H0", host[0].Name) resourcepool := make([]mo.ResourcePool, 0) - err = f.find(t.Context(), "ResourcePool", "/DC0/host/DC0_C0/Resources/DC0_C0_RP0", &resourcepool) + err = f.find(t.Context(), "ResourcePool", "/DC0/host/DC0_C0/Resources/DC0_C0_RP0", &resourcepool, []string{}) require.NoError(t, err) require.Len(t, host, 1) require.Equal(t, "DC0_C0_H0", host[0].Name) host = make([]mo.HostSystem, 0) - err = f.find(t.Context(), "HostSystem", "/DC0/host/DC0_C0/*", &host) + err = f.find(t.Context(), "HostSystem", "/DC0/host/DC0_C0/*", &host, []string{}) require.NoError(t, err) require.Len(t, host, 3) var vm []mo.VirtualMachine - testLookupVM(t.Context(), t, &f, "/DC0/vm/DC0_H0_VM0", 1, "") - testLookupVM(t.Context(), t, &f, "/DC0/vm/DC0_C0*", 2, "") - testLookupVM(t.Context(), t, &f, "/DC0/*/DC0_H0_VM0", 1, "DC0_H0_VM0") - testLookupVM(t.Context(), t, &f, "/DC0/*/DC0_H0_*", 2, "") - testLookupVM(t.Context(), t, &f, "/DC0/**/DC0_H0_VM*", 2, "") - testLookupVM(t.Context(), t, &f, "/DC0/**", 4, "") - testLookupVM(t.Context(), t, &f, "/DC1/**", 4, "") - testLookupVM(t.Context(), t, &f, "/**", 8, "") - testLookupVM(t.Context(), t, &f, "/**/vm/**", 8, "") - testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*", 8, "") - testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*VM*", 8, "") - testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*/*/*DC*", 4, "") + testLookupVM(t.Context(), t, &f, "/DC0/vm/DC0_H0_VM0", 1, "", []string{}) + testLookupVM(t.Context(), t, &f, "/DC0/vm/DC0_C0*", 2, "", []string{}) + testLookupVM(t.Context(), t, &f, "/DC0/*/DC0_H0_VM0", 1, "DC0_H0_VM0", []string{}) + testLookupVM(t.Context(), t, &f, "/DC0/*/DC0_H0_*", 2, "", []string{}) + testLookupVM(t.Context(), t, &f, "/DC0/**/DC0_H0_VM*", 2, "", []string{}) + testLookupVM(t.Context(), t, &f, "/DC0/**", 4, "", []string{}) + testLookupVM(t.Context(), t, &f, "/DC1/**", 4, "", []string{}) + testLookupVM(t.Context(), t, &f, "/**", 8, "", []string{}) + testLookupVM(t.Context(), t, &f, "/**/vm/**", 8, "", []string{}) + testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*", 8, "", []string{}) + testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*VM*", 8, "", []string{}) + testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*/*/*DC*", 4, "", []string{}) + testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*/*/*DC*", 4, "", []string{"runtime.powerState"}) vm = make([]mo.VirtualMachine, 0) - err = f.findAll(t.Context(), "VirtualMachine", []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, nil, &vm) + err = f.findAll(t.Context(), "VirtualMachine", []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, nil, &vm, []string{}) require.NoError(t, err) require.Len(t, vm, 4) @@ -389,20 +390,20 @@ func TestFolders(t *testing.T) { f := finder{c} var folder []mo.Folder - err = f.find(t.Context(), "Folder", "/F0", &folder) + err = f.find(t.Context(), "Folder", "/F0", &folder, []string{}) require.NoError(t, err) require.Len(t, folder, 1) require.Equal(t, "F0", folder[0].Name) var dc []mo.Datacenter - err = f.find(t.Context(), "Datacenter", "/F0/DC1", &dc) + err = f.find(t.Context(), "Datacenter", "/F0/DC1", &dc, []string{}) require.NoError(t, err) require.Len(t, dc, 1) require.Equal(t, "DC1", dc[0].Name) - testLookupVM(t.Context(), t, &f, "/F0/DC0/vm/**/F*", 0, "") - testLookupVM(t.Context(), t, &f, "/F0/DC1/vm/**/F*/*VM*", 4, "") - testLookupVM(t.Context(), t, &f, "/F0/DC1/vm/**/F*/**", 4, "") + testLookupVM(t.Context(), t, &f, "/F0/DC0/vm/**/F*", 0, "", []string{}) + testLookupVM(t.Context(), t, &f, "/F0/DC1/vm/**/F*/*VM*", 4, "", []string{}) + testLookupVM(t.Context(), t, &f, "/F0/DC1/vm/**/F*/**", 4, "", []string{}) } func TestVsanCmmds(t *testing.T) { @@ -417,7 +418,7 @@ func TestVsanCmmds(t *testing.T) { f := finder{c} var clusters []mo.ClusterComputeResource - err = f.findAll(t.Context(), "ClusterComputeResource", []string{"/**"}, nil, &clusters) + err = f.findAll(t.Context(), "ClusterComputeResource", []string{"/**"}, nil, &clusters, []string{}) require.NoError(t, err) clusterObj := object.NewClusterComputeResource(c.client.Client, clusters[0].Reference()) @@ -524,7 +525,7 @@ func testCollection(t *testing.T, excludeClusters bool) { // We have to follow the host parent path to locate a cluster. Look up the host! finder := finder{client} var hosts []mo.HostSystem - err := finder.find(t.Context(), "HostSystem", "/**/"+hostName, &hosts) + err := finder.find(t.Context(), "HostSystem", "/**/"+hostName, &hosts, []string{}) require.NoError(t, err) require.NotEmpty(t, hosts) hostMoid = hosts[0].Reference().Value From f86e85f61a5c8e20f66fede72403a51da5e458a0 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Mon, 29 Jun 2026 15:36:58 +0200 Subject: [PATCH 02/23] feat(inputs.vsphere) : add custom properties --- plugins/inputs/vsphere/README.md | 9 ++++-- plugins/inputs/vsphere/endpoint.go | 50 +++++++++++++++--------------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/plugins/inputs/vsphere/README.md b/plugins/inputs/vsphere/README.md index 1be2a87cd73ab..ee93e3393995b 100644 --- a/plugins/inputs/vsphere/README.md +++ b/plugins/inputs/vsphere/README.md @@ -82,6 +82,9 @@ to use them. ] # vm_metric_exclude = [] ## Nothing is excluded by default # vm_instances = true ## true by default + ## Custom VM properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # vm_propertie_include = ["runtime.powerState", "summary.config.MemorySizeMB", "summary.config.MemoryReservation"] ## Hosts ## Typical host metrics (if omitted or empty, all metrics are collected) @@ -140,7 +143,9 @@ to use them. # host_metric_exclude = [] ## Nothing excluded by default # host_instances = true ## true by default - + ## Custom host properties to collect. + ## If the properties are text-based, they will be added as tags; otherwise, they will be added as metrics. + # host_propertie_include = ["summary.runtime.powerState", "summary.overallStatus", "summary.runtime.inMaintenanceMode", "summary.runtime.connectionState"] ## Clusters # cluster_include = [ "/*/host/**"] # Inventory path to clusters to collect (by default all are collected) @@ -986,8 +991,6 @@ It is recommanded to use it with processors.enum to convert as metric. For sample : -For sample : - ```toml [[processors.enum]] [[processors.enum.mapping]] diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index a2c84dd77f9d1..e296a3dbaf5e1 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -84,7 +84,7 @@ type resourceKind struct { parent string latestSample time.Time lastColl time.Time - PropertieInclude []string + propertieInclude []string } type metricEntry struct { @@ -151,7 +151,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.DatacenterInstances, getObjects: getDatacenters, parent: "", - PropertieInclude: parent.DatacenterPropertieInclude, + propertieInclude: parent.DatacenterpropertieInclude, }, "cluster": { name: "cluster", @@ -170,7 +170,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.ClusterInstances, getObjects: getClusters, parent: "datacenter", - PropertieInclude: parent.ClusterPropertieInclude, + propertieInclude: parent.ClusterpropertieInclude, }, "resourcepool": { name: "resourcepool", @@ -189,7 +189,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.ResourcePoolInstances, getObjects: getResourcePools, parent: "cluster", - PropertieInclude: parent.ResourcePoolPropertieInclude, + propertieInclude: parent.ResourcePoolpropertieInclude, }, "host": { name: "host", @@ -208,7 +208,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.HostInstances, getObjects: getHosts, parent: "cluster", - PropertieInclude: parent.HostPropertieInclude, + propertieInclude: parent.HostpropertieInclude, }, "vm": { name: "vm", @@ -227,7 +227,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.VMInstances, getObjects: getVMs, parent: "host", - PropertieInclude: parent.VMPropertieInclude, + propertieInclude: parent.VMpropertieInclude, }, "datastore": { name: "datastore", @@ -245,7 +245,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.DatastoreInstances, getObjects: getDatastores, parent: "", - PropertieInclude: parent.DatastorePropertieInclude, + propertieInclude: parent.DatastorepropertieInclude, }, "vsan": { name: "vsan", @@ -263,7 +263,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: false, getObjects: getClusters, parent: "datacenter", - PropertieInclude: parent.VSANPropertieInclude, + propertieInclude: parent.VSANpropertieInclude, }, } @@ -492,10 +492,10 @@ func (e *endpoint) discover(ctx context.Context) error { resType: res.vcName, paths: res.paths, excludePaths: res.excludePaths, - custoFields: res.PropertieInclude} + custoFields: res.propertieInclude} ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) - objects, err := res.getObjects(ctx1, e, &rf, res.PropertieInclude) + objects, err := res.getObjects(ctx1, e, &rf, res.propertieInclude) cancel1() if err != nil { return err @@ -647,7 +647,7 @@ func (e *endpoint) complexMetadataSelect(ctx context.Context, res *resourceKind, te.wait() } -func getDatacenters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { +func getDatacenters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.Datacenter ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() @@ -665,13 +665,13 @@ func getDatacenters(ctx context.Context, e *endpoint, resourceFilter *resourceFi parentRef: r.Parent, dcname: r.Name, customValues: e.loadCustomAttributes(r.ManagedEntity), - customProperties: e.loadCustomProperties(r, PropertieInclude), + customProperties: e.loadCustomProperties(r, propertieInclude), } } return m, nil } -func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { +func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.ClusterComputeResource ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() @@ -714,7 +714,7 @@ func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilte ref: r.ExtensibleManagedObject.Reference(), parentRef: p, customValues: e.loadCustomAttributes(r.ManagedEntity), - customProperties: e.loadCustomProperties(r, PropertieInclude), + customProperties: e.loadCustomProperties(r, propertieInclude), } return nil }() @@ -726,7 +726,7 @@ func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilte } // noinspection GoUnusedParameter -func getResourcePools(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { +func getResourcePools(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.ResourcePool err := resourceFilter.findAll(ctx, &resources) if err != nil { @@ -741,7 +741,7 @@ func getResourcePools(ctx context.Context, e *endpoint, resourceFilter *resource ref: r.ExtensibleManagedObject.Reference(), parentRef: r.Parent, customValues: e.loadCustomAttributes(r.ManagedEntity), - customProperties: e.loadCustomProperties(r, PropertieInclude), + customProperties: e.loadCustomProperties(r, propertieInclude), } } return m, nil @@ -758,7 +758,7 @@ func getResourcePoolName(rp types.ManagedObjectReference, rps objectMap) string } // noinspection GoUnusedParameter -func getHosts(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { +func getHosts(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.HostSystem err := resourceFilter.findAll(ctx, &resources) if err != nil { @@ -775,14 +775,14 @@ func getHosts(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, ref: r.ExtensibleManagedObject.Reference(), parentRef: r.Parent, customValues: e.loadCustomAttributes(r.ManagedEntity), - customProperties: e.loadCustomProperties(r, PropertieInclude), + customProperties: e.loadCustomProperties(r, propertieInclude), lookup: lookup, } } return m, nil } -func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter, PropertieInclude []string) (objectMap, error) { +func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.VirtualMachine ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() @@ -870,14 +870,14 @@ func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter, PropertieInclu altID: uuid, rpname: rpname, customValues: e.loadCustomAttributes(r.ManagedEntity), - customProperties: e.loadCustomProperties(r, PropertieInclude), + customProperties: e.loadCustomProperties(r, propertieInclude), lookup: lookup, } } return m, nil } -func getDatastores(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, PropertieInclude []string) (objectMap, error) { +func getDatastores(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.Datastore ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() @@ -902,7 +902,7 @@ func getDatastores(ctx context.Context, e *endpoint, resourceFilter *resourceFil parentRef: r.Parent, altID: lunID, customValues: e.loadCustomAttributes(r.ManagedEntity), - customProperties: e.loadCustomProperties(r, PropertieInclude), + customProperties: e.loadCustomProperties(r, propertieInclude), } } return m, nil @@ -931,10 +931,10 @@ func (e *endpoint) loadCustomAttributes(entity mo.ManagedEntity) map[string]stri return cvs } -func (e *endpoint) loadCustomProperties(entity interface{}, PropertieInclude []string) map[string]interface{} { +func (e *endpoint) loadCustomProperties(entity interface{}, propertieInclude []string) map[string]interface{} { cvs := make(map[string]interface{}) - if len(PropertieInclude) != 0 { - for _, filtre := range PropertieInclude { + if len(propertieInclude) != 0 { + for _, filtre := range propertieInclude { if valeur, ok := e.getExtraData(entity, capitalizeAfterDot(filtre)); ok { key := e.makePropertyIdentifier(filtre) cvs[key] = valeur From cd2c9f0122e257102a3cbcd6462c273cdc5dd8cc Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Tue, 30 Jun 2026 12:05:48 +0200 Subject: [PATCH 03/23] feat(inputs.vsphere): add custom properties --- plugins/inputs/vsphere/endpoint.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index e296a3dbaf5e1..2119579afb579 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -151,7 +151,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.DatacenterInstances, getObjects: getDatacenters, parent: "", - propertieInclude: parent.DatacenterpropertieInclude, + propertieInclude: parent.DatacenterPropertieInclude, }, "cluster": { name: "cluster", @@ -170,7 +170,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.ClusterInstances, getObjects: getClusters, parent: "datacenter", - propertieInclude: parent.ClusterpropertieInclude, + propertieInclude: parent.ClusterPropertieInclude, }, "resourcepool": { name: "resourcepool", @@ -189,7 +189,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.ResourcePoolInstances, getObjects: getResourcePools, parent: "cluster", - propertieInclude: parent.ResourcePoolpropertieInclude, + propertieInclude: parent.ResourcePoolPropertieInclude, }, "host": { name: "host", @@ -208,7 +208,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.HostInstances, getObjects: getHosts, parent: "cluster", - propertieInclude: parent.HostpropertieInclude, + propertieInclude: parent.HostPropertieInclude, }, "vm": { name: "vm", @@ -227,7 +227,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.VMInstances, getObjects: getVMs, parent: "host", - propertieInclude: parent.VMpropertieInclude, + propertieInclude: parent.VMPropertieInclude, }, "datastore": { name: "datastore", @@ -245,7 +245,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: parent.DatastoreInstances, getObjects: getDatastores, parent: "", - propertieInclude: parent.DatastorepropertieInclude, + propertieInclude: parent.DatastorePropertieInclude, }, "vsan": { name: "vsan", @@ -263,7 +263,7 @@ func newEndpoint(ctx context.Context, parent *VSphere, address *url.URL, log tel collectInstances: false, getObjects: getClusters, parent: "datacenter", - propertieInclude: parent.VSANpropertieInclude, + propertieInclude: parent.VSANPropertieInclude, }, } From 8229c22b93c8b6b2e1863ebc8a29716c4a8d5fdc Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Tue, 30 Jun 2026 12:56:57 +0200 Subject: [PATCH 04/23] Correction gocritic --- plugins/inputs/vsphere/endpoint.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index 2119579afb579..4d735951a0039 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -1390,22 +1390,12 @@ func (e *endpoint) populateTags(objectRef *objectRef, resourceType string, resou if objectRef.guest != "" { t["guest"] = objectRef.guest } - // if objectRef.lookup["powerstate"] != "" { - // t["powerstate"] = objectRef.lookup["powerstate"] - // } if gh := objectRef.lookup["guesthostname"]; gh != "" { t["guesthostname"] = gh } if c, ok := e.resourceKinds["cluster"].objects[parent.parentRef.Value]; ok { t["clustername"] = c.name } - } else if resourceType == "host" { - // if objectRef.lookup["powerstate"] != "" { - // t["powerstate"] = objectRef.lookup["powerstate"] - // } - // if objectRef.lookup["maintenance"] != "" { - // t["maintenance"] = objectRef.lookup["maintenance"] - // } } // Fill in Datacenter name From f6d7d90ba6786a615cb132c660fea4eff2afa8ad Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Tue, 30 Jun 2026 13:23:15 +0200 Subject: [PATCH 05/23] Corrige syntaxe --- plugins/inputs/vsphere/endpoint.go | 35 ------------------------------ 1 file changed, 35 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index 4d735951a0039..a3d19a0a74b8f 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -1489,23 +1489,6 @@ func round(x float64) float64 { return t } -func getIndex(enumObj interface{}) int { - v := reflect.ValueOf(enumObj) - - // Vérifier si le type a une méthode Values() - method := v.MethodByName("Values") - if !method.IsValid() { - // La méthode n'existe pas - return -1 - } - for i, s := range method.Call(nil) { - if s == enumObj { - return i - } - } - return -1 // si non trouvé -} - func (e *endpoint) getExtraData(entity interface{}, fieldPath string) (interface{}, bool) { v := reflect.ValueOf(entity) @@ -1545,24 +1528,6 @@ func (e *endpoint) getExtraData(entity interface{}, fieldPath string) (interface return nil, false } -func (e *endpoint) printStructFields(t reflect.Type, prefix string) { - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - if t.Kind() != reflect.Struct { - e.parent.Log.Warnf("Not a struct:", t) - return - } - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - e.parent.Log.Warnf(prefix, "Field:", field.Name, "Type:", field.Type) - // Si vous voulez explorer récursivement - if field.Type.Kind() == reflect.Struct { - e.printStructFields(field.Type, prefix+">") - } - } -} - func (e *endpoint) makePropertyIdentifier(input string) string { var result []rune runes := []rune(e.parent.Separator) From 7c879d628c86eebbdf271faadaff4156aa90b902 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Tue, 30 Jun 2026 13:53:22 +0200 Subject: [PATCH 06/23] Corrige syntax --- plugins/inputs/vsphere/endpoint.go | 35 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index a3d19a0a74b8f..dc1c99112c555 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -801,7 +801,7 @@ func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter, propertieInclu resType: "ResourcePool", paths: []string{"/*/host/**"}, excludePaths: nil} - resourcePools, err := getResourcePools(ctx, e, &rprf, []string{}) + resourcePools, err := getResourcePools(ctx, e, &rprf, make([]string, 0)) if err != nil { return nil, err } @@ -1283,23 +1283,23 @@ func (e *endpoint) collectChunk( nValues := 0 alignedInfo, alignedValues := e.alignSamples(em.SampleInfo, v.Value, interval) - globalFields := e.populateGlobalFields(objectRef) + globalFields := populateGlobalFields(objectRef) if len(globalFields) != 0 { mn, fn := e.makeMetricIdentifier(prefix, "internal") bKey := mn + " " + v.Instance + " " + strconv.FormatInt(latestSample.UnixNano(), 10) - bucket, found := buckets[bKey] + _, found := buckets[bKey] if !found { fields := make(map[string]interface{}) fields[fn] = int64(1.0) - tags := map[string]string{} + tags := make(map[string]string) for k, v := range t { tags[k] = v } for k, v := range globalFields { tags[k] = fmt.Sprintf("%v", v) } - bucket = metricEntry{name: mn, ts: latestSample, fields: fields, tags: tags} + bucket := metricEntry{name: mn, ts: latestSample, fields: fields, tags: tags} buckets[bKey] = bucket } } @@ -1456,7 +1456,7 @@ func (e *endpoint) populateTags(objectRef *objectRef, resourceType string, resou } } -func (e *endpoint) populateGlobalFields(objectRef *objectRef) map[string]interface{} { +func populateGlobalFields(objectRef *objectRef) map[string]interface{} { globalFields := make(map[string]interface{}) for k, v := range objectRef.customProperties { globalFields[k] = v @@ -1503,21 +1503,20 @@ func (e *endpoint) getExtraData(entity interface{}, fieldPath string) (interface fields := strings.Split(fieldPath, ".") for _, field := range fields { - if v.Kind() == reflect.Struct { - v = v.FieldByName(field) - // Si le champ n'existe pas ou n'est pas accessible - if !v.IsValid() { - e.parent.Log.Warnf("Field %s in %s of %s not valid. Skipping", field, fieldPath, reflect.TypeOf(entity)) - return nil, false - } - // Si c'est un pointeur, dé-référencer - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - } else { + if v.Kind() != reflect.Struct { e.parent.Log.Warnf("Field %s in %s of %s not struct %s. Skipping", field, fieldPath, reflect.TypeOf(entity), v.Kind()) return nil, false } + v = v.FieldByName(field) + // Si le champ n'existe pas ou n'est pas accessible + if !v.IsValid() { + e.parent.Log.Warnf("Field %s in %s of %s not valid. Skipping", field, fieldPath, reflect.TypeOf(entity)) + return nil, false + } + // Si c'est un pointeur, dé-référencer + if v.Kind() == reflect.Ptr { + v = v.Elem() + } } // Retourner la valeur sous forme de string From 174e995915a2207c881bd23b9cae000d6b3bb8f6 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 1 Jul 2026 18:56:25 +0200 Subject: [PATCH 07/23] Modify the syntax --- plugins/inputs/vsphere/endpoint.go | 31 ++++---- plugins/inputs/vsphere/finder.go | 50 +++++++----- plugins/inputs/vsphere/vsphere_test.go | 101 +++++++++++++++++-------- 3 files changed, 115 insertions(+), 67 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index dc1c99112c555..c8ebef9c2804f 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -492,7 +492,8 @@ func (e *endpoint) discover(ctx context.Context) error { resType: res.vcName, paths: res.paths, excludePaths: res.excludePaths, - custoFields: res.propertieInclude} + custoFields: res.propertieInclude, + } ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) objects, err := res.getObjects(ctx1, e, &rf, res.propertieInclude) @@ -647,11 +648,11 @@ func (e *endpoint) complexMetadataSelect(ctx context.Context, res *resourceKind, te.wait() } -func getDatacenters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { +func getDatacenters(ctx context.Context, e *endpoint, rf *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.Datacenter ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() - err := resourceFilter.findAll(ctx1, &resources) + err := rf.findAll(ctx1, &resources) if err != nil { return nil, err } @@ -671,11 +672,11 @@ func getDatacenters(ctx context.Context, e *endpoint, resourceFilter *resourceFi return m, nil } -func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { +func getClusters(ctx context.Context, e *endpoint, rf *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.ClusterComputeResource ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() - err := resourceFilter.findAll(ctx1, &resources) + err := rf.findAll(ctx1, &resources) if err != nil { return nil, err } @@ -726,9 +727,9 @@ func getClusters(ctx context.Context, e *endpoint, resourceFilter *resourceFilte } // noinspection GoUnusedParameter -func getResourcePools(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { +func getResourcePools(ctx context.Context, e *endpoint, rf *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.ResourcePool - err := resourceFilter.findAll(ctx, &resources) + err := rf.findAll(ctx, &resources) if err != nil { return nil, err } @@ -758,9 +759,9 @@ func getResourcePoolName(rp types.ManagedObjectReference, rps objectMap) string } // noinspection GoUnusedParameter -func getHosts(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { +func getHosts(ctx context.Context, e *endpoint, rf *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.HostSystem - err := resourceFilter.findAll(ctx, &resources) + err := rf.findAll(ctx, &resources) if err != nil { return nil, err } @@ -796,12 +797,14 @@ func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter, propertieInclu return nil, err } // Create a ResourcePool Filter and get the list of Resource Pools - rprf := resourceFilter{ + rfrp := resourceFilter{ finder: &finder{client}, resType: "ResourcePool", paths: []string{"/*/host/**"}, - excludePaths: nil} - resourcePools, err := getResourcePools(ctx, e, &rprf, make([]string, 0)) + excludePaths: nil, + custoFields: nil, + } + resourcePools, err := getResourcePools(ctx, e, &rfrp, make([]string, 0)) if err != nil { return nil, err } @@ -877,11 +880,11 @@ func getVMs(ctx context.Context, e *endpoint, rf *resourceFilter, propertieInclu return m, nil } -func getDatastores(ctx context.Context, e *endpoint, resourceFilter *resourceFilter, propertieInclude []string) (objectMap, error) { +func getDatastores(ctx context.Context, e *endpoint, rf *resourceFilter, propertieInclude []string) (objectMap, error) { var resources []mo.Datastore ctx1, cancel1 := context.WithTimeout(ctx, time.Duration(e.parent.Timeout)) defer cancel1() - err := resourceFilter.findAll(ctx1, &resources) + err := rf.findAll(ctx1, &resources) if err != nil { return nil, err } diff --git a/plugins/inputs/vsphere/finder.go b/plugins/inputs/vsphere/finder.go index d18012addc2ee..7465bb3743a34 100644 --- a/plugins/inputs/vsphere/finder.go +++ b/plugins/inputs/vsphere/finder.go @@ -32,18 +32,24 @@ type resourceFilter struct { custoFields []string } +// resourceInfo is a utility class grouping a type and relevant parameters. +type resourceInfo struct { + resType string + custoFields []string +} + // findAll returns the union of resources found given the supplied resource type and paths. -func (f *finder) findAll(ctx context.Context, resType string, paths, excludePaths []string, dst interface{}, custoFields []string) error { +func (f *finder) findAll(ctx context.Context, resourceInfo resourceInfo, paths, excludePaths []string, dst interface{}) error { objs := make(map[string]types.ObjectContent) for _, p := range paths { - if err := f.findResources(ctx, resType, p, objs, custoFields); err != nil { + if err := f.findResources(ctx, resourceInfo, p, objs); err != nil { return err } } if len(excludePaths) > 0 { excludes := make(map[string]types.ObjectContent) for _, p := range excludePaths { - if err := f.findResources(ctx, resType, p, excludes, custoFields); err != nil { + if err := f.findResources(ctx, resourceInfo, p, excludes); err != nil { return err } } @@ -55,31 +61,31 @@ func (f *finder) findAll(ctx context.Context, resType string, paths, excludePath } // find returns the resources matching the specified path. -func (f *finder) find(ctx context.Context, resType, path string, dst interface{}, custoFields []string) error { +func (f *finder) find(ctx context.Context, resourceInfo resourceInfo, path string, dst interface{}) error { objs := make(map[string]types.ObjectContent) - err := f.findResources(ctx, resType, path, objs, custoFields) + err := f.findResources(ctx, resourceInfo, path, objs) if err != nil { return err } return objectContentToTypedArray(objs, dst) } -func (f *finder) findResources(ctx context.Context, resType, path string, objs map[string]types.ObjectContent, custoFields []string) error { +func (f *finder) findResources(ctx context.Context, resourceInfo resourceInfo, path string, objs map[string]types.ObjectContent) error { p := strings.Split(path, "/") flt := make([]property.Match, len(p)-1) for i := 1; i < len(p); i++ { flt[i-1] = property.Match{"name": p[i]} } - err := f.descend(ctx, f.client.client.ServiceContent.RootFolder, resType, flt, 0, objs, custoFields) + err := f.descend(ctx, f.client.client.ServiceContent.RootFolder, resourceInfo, flt, 0, objs) if err != nil { return err } - f.client.log.Debugf("Find(%s, %s) returned %d objects", resType, path, len(objs)) + f.client.log.Debugf("Find(%s, %s) returned %d objects", resourceInfo.resType, path, len(objs)) return nil } -func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, resType string, - tokens []property.Match, pos int, objs map[string]types.ObjectContent, custoFields []string) error { +func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, resourceInfo resourceInfo, + tokens []property.Match, pos int, objs map[string]types.ObjectContent) error { isLeaf := pos == len(tokens)-1 // No more tokens to match? @@ -108,20 +114,20 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, objectTypes := ct if isLeaf { - if af, ok := addFields[resType]; ok { + if af, ok := addFields[resourceInfo.resType]; ok { fields = append(fields, af...) } - fields = append(fields, custoFields...) + fields = append(fields, resourceInfo.custoFields...) uniqueFields := removeDuplicates(fields) if recurse { // Special case: The last token is a recursive wildcard, so we can grab everything // recursively in a single call. - v2, err := m.CreateContainerView(ctx, root, []string{resType}, true) + v2, err := m.CreateContainerView(ctx, root, []string{resourceInfo.resType}, true) if err != nil { return err } defer v2.Destroy(ctx) //nolint:errcheck // Ignore the returned error as we cannot do anything about it anyway - err = v2.Retrieve(ctx, []string{resType}, uniqueFields, &content) + err = v2.Retrieve(ctx, []string{resourceInfo.resType}, uniqueFields, &content) if err != nil { return err } @@ -130,7 +136,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, } return nil } - objectTypes = []string{resType} // Only load wanted object type at leaf level + objectTypes = []string{resourceInfo.resType} // Only load wanted object type at leaf level } err = v.Retrieve(ctx, objectTypes, fields, &content) if err != nil { @@ -148,7 +154,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, continue } - if c.Obj.Type == resType && isLeaf { + if c.Obj.Type == resourceInfo.resType && isLeaf { // We found what we're looking for. Consider it a leaf and stop descending objs[c.Obj.String()] = c continue @@ -164,7 +170,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, // Rerun the entire level as a leaf. This is needed since all properties aren't loaded // when we're processing non-leaf nodes. if pos == len(tokens)-2 { - if c.Obj.Type == resType { + if c.Obj.Type == resourceInfo.resType { rerunAsLeaf = true continue } @@ -178,7 +184,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, // The normal case: Advance to next token before descending inc = 1 } - err := f.descend(ctx, c.Obj, resType, tokens, pos+inc, objs, custoFields) + err := f.descend(ctx, c.Obj, resourceInfo, tokens, pos+inc, objs) if err != nil { return err } @@ -188,7 +194,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, // We're at a "pseudo leaf", i.e. we looked ahead a token and found that this level contains leaf nodes. // Rerun the entire level as a leaf to get those nodes. This will only be executed when pos is one token // before the last, to pos+1 will always point to a leaf token. - return f.descend(ctx, root, resType, tokens, pos+1, objs, custoFields) + return f.descend(ctx, root, resourceInfo, tokens, pos+1, objs) } return nil @@ -227,7 +233,11 @@ func objectContentToTypedArray(objs map[string]types.ObjectContent, dst interfac // findAll finds all resources matching the paths that were specified upon creation of the resourceFilter. func (r *resourceFilter) findAll(ctx context.Context, dst interface{}) error { - return r.finder.findAll(ctx, r.resType, r.paths, r.excludePaths, dst, r.custoFields) + resourceInfo := resourceInfo{ + resType: r.resType, + custoFields: r.custoFields} + + return r.finder.findAll(ctx, resourceInfo, r.paths, r.excludePaths, dst) } func matchName(f property.Match, props []types.DynamicProperty) bool { diff --git a/plugins/inputs/vsphere/vsphere_test.go b/plugins/inputs/vsphere/vsphere_test.go index f20e0da3bfd8c..3185f6fac95a0 100644 --- a/plugins/inputs/vsphere/vsphere_test.go +++ b/plugins/inputs/vsphere/vsphere_test.go @@ -242,19 +242,19 @@ func TestMaxQuery(t *testing.T) { c2.close() } -func testLookupVM(ctx context.Context, t *testing.T, f *finder, path string, expected int, expectedName string, custoFields []string) { - poweredOn := types.VirtualMachinePowerState("poweredOn") +func testLookupVM(ctx context.Context, t *testing.T, f *finder, path string, expected int, expectedName string) { var vm []mo.VirtualMachine - err := f.find(ctx, "VirtualMachine", path, &vm, custoFields) + ri := resourceInfo{ + resType: "VirtualMachine", + custoFields: nil, + } + + err := f.find(ctx, ri, path, &vm) require.NoError(t, err) require.Len(t, vm, expected) if expectedName != "" { require.Equal(t, expectedName, vm[0].Name) } - for i := range vm { - v := &vm[i] - require.Equal(t, poweredOn, v.Runtime.PowerState) - } } func TestFinder(t *testing.T) { @@ -274,51 +274,70 @@ func TestFinder(t *testing.T) { f := finder{c} var dc []mo.Datacenter - err = f.find(t.Context(), "Datacenter", "/DC0", &dc, []string{}) + ri := resourceInfo{ + resType: "Datacenter", + custoFields: nil, + } + err = f.find(t.Context(), ri, "/DC0", &dc) require.NoError(t, err) require.Len(t, dc, 1) require.Equal(t, "DC0", dc[0].Name) var host []mo.HostSystem - err = f.find(t.Context(), "HostSystem", "/DC0/host/DC0_H0/DC0_H0", &host, []string{}) + ri = resourceInfo{ + resType: "HostSystem", + custoFields: nil, + } + err = f.find(t.Context(), ri, "/DC0/host/DC0_H0/DC0_H0", &host) require.NoError(t, err) require.Len(t, host, 1) require.Equal(t, "DC0_H0", host[0].Name) host = make([]mo.HostSystem, 0) - err = f.find(t.Context(), "HostSystem", "/DC0/host/DC0_C0/DC0_C0_H0", &host, []string{}) + err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/DC0_C0_H0", &host) require.NoError(t, err) require.Len(t, host, 1) require.Equal(t, "DC0_C0_H0", host[0].Name) resourcepool := make([]mo.ResourcePool, 0) - err = f.find(t.Context(), "ResourcePool", "/DC0/host/DC0_C0/Resources/DC0_C0_RP0", &resourcepool, []string{}) + ri = resourceInfo{ + resType: "ResourcePool", + custoFields: nil, + } + err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/Resources/DC0_C0_RP0", &resourcepool) require.NoError(t, err) require.Len(t, host, 1) require.Equal(t, "DC0_C0_H0", host[0].Name) host = make([]mo.HostSystem, 0) - err = f.find(t.Context(), "HostSystem", "/DC0/host/DC0_C0/*", &host, []string{}) + ri = resourceInfo{ + resType: "HostSystem", + custoFields: nil, + } + err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/*", &host) require.NoError(t, err) require.Len(t, host, 3) var vm []mo.VirtualMachine - testLookupVM(t.Context(), t, &f, "/DC0/vm/DC0_H0_VM0", 1, "", []string{}) - testLookupVM(t.Context(), t, &f, "/DC0/vm/DC0_C0*", 2, "", []string{}) - testLookupVM(t.Context(), t, &f, "/DC0/*/DC0_H0_VM0", 1, "DC0_H0_VM0", []string{}) - testLookupVM(t.Context(), t, &f, "/DC0/*/DC0_H0_*", 2, "", []string{}) - testLookupVM(t.Context(), t, &f, "/DC0/**/DC0_H0_VM*", 2, "", []string{}) - testLookupVM(t.Context(), t, &f, "/DC0/**", 4, "", []string{}) - testLookupVM(t.Context(), t, &f, "/DC1/**", 4, "", []string{}) - testLookupVM(t.Context(), t, &f, "/**", 8, "", []string{}) - testLookupVM(t.Context(), t, &f, "/**/vm/**", 8, "", []string{}) - testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*", 8, "", []string{}) - testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*VM*", 8, "", []string{}) - testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*/*/*DC*", 4, "", []string{}) - testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*/*/*DC*", 4, "", []string{"runtime.powerState"}) + testLookupVM(t.Context(), t, &f, "/DC0/vm/DC0_H0_VM0", 1, "") + testLookupVM(t.Context(), t, &f, "/DC0/vm/DC0_C0*", 2, "") + testLookupVM(t.Context(), t, &f, "/DC0/*/DC0_H0_VM0", 1, "DC0_H0_VM0") + testLookupVM(t.Context(), t, &f, "/DC0/*/DC0_H0_*", 2, "") + testLookupVM(t.Context(), t, &f, "/DC0/**/DC0_H0_VM*", 2, "") + testLookupVM(t.Context(), t, &f, "/DC0/**", 4, "") + testLookupVM(t.Context(), t, &f, "/DC1/**", 4, "") + testLookupVM(t.Context(), t, &f, "/**", 8, "") + testLookupVM(t.Context(), t, &f, "/**/vm/**", 8, "") + testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*", 8, "") + testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*VM*", 8, "") + testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*/*/*DC*", 4, "") vm = make([]mo.VirtualMachine, 0) - err = f.findAll(t.Context(), "VirtualMachine", []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, nil, &vm, []string{}) + ri = resourceInfo{ + resType: "VirtualMachine", + custoFields: nil, + } + err = f.findAll(t.Context(), ri, []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, nil, &vm) require.NoError(t, err) require.Len(t, vm, 4) @@ -390,20 +409,28 @@ func TestFolders(t *testing.T) { f := finder{c} var folder []mo.Folder - err = f.find(t.Context(), "Folder", "/F0", &folder, []string{}) + ri := resourceInfo{ + resType: "Folder", + custoFields: nil, + } + err = f.find(t.Context(), ri, "/F0", &folder) require.NoError(t, err) require.Len(t, folder, 1) require.Equal(t, "F0", folder[0].Name) var dc []mo.Datacenter - err = f.find(t.Context(), "Datacenter", "/F0/DC1", &dc, []string{}) + ri = resourceInfo{ + resType: "Datacenter", + custoFields: nil, + } + err = f.find(t.Context(), ri, "/F0/DC1", &dc) require.NoError(t, err) require.Len(t, dc, 1) require.Equal(t, "DC1", dc[0].Name) - testLookupVM(t.Context(), t, &f, "/F0/DC0/vm/**/F*", 0, "", []string{}) - testLookupVM(t.Context(), t, &f, "/F0/DC1/vm/**/F*/*VM*", 4, "", []string{}) - testLookupVM(t.Context(), t, &f, "/F0/DC1/vm/**/F*/**", 4, "", []string{}) + testLookupVM(t.Context(), t, &f, "/F0/DC0/vm/**/F*", 0, "") + testLookupVM(t.Context(), t, &f, "/F0/DC1/vm/**/F*/*VM*", 4, "") + testLookupVM(t.Context(), t, &f, "/F0/DC1/vm/**/F*/**", 4, "") } func TestVsanCmmds(t *testing.T) { @@ -418,7 +445,11 @@ func TestVsanCmmds(t *testing.T) { f := finder{c} var clusters []mo.ClusterComputeResource - err = f.findAll(t.Context(), "ClusterComputeResource", []string{"/**"}, nil, &clusters, []string{}) + ri := resourceInfo{ + resType: "ClusterComputeResource", + custoFields: nil, + } + err = f.findAll(t.Context(), ri, []string{"/**"}, nil, &clusters) require.NoError(t, err) clusterObj := object.NewClusterComputeResource(c.client.Client, clusters[0].Reference()) @@ -514,6 +545,10 @@ func testCollection(t *testing.T, excludeClusters bool) { client, err := v.endpoints[0].clientFactory.getClient(t.Context()) require.NoError(t, err) hostCache := make(map[string]string) + ri := resourceInfo{ + resType: "HostSystem", + custoFields: nil, + } for _, m := range acc.Metrics { delete(mustHaveMetrics, m.Measurement) @@ -525,7 +560,7 @@ func testCollection(t *testing.T, excludeClusters bool) { // We have to follow the host parent path to locate a cluster. Look up the host! finder := finder{client} var hosts []mo.HostSystem - err := finder.find(t.Context(), "HostSystem", "/**/"+hostName, &hosts, []string{}) + err := finder.find(t.Context(), ri, "/**/"+hostName, &hosts) require.NoError(t, err) require.NotEmpty(t, hosts) hostMoid = hosts[0].Reference().Value From 2480e212073980bc01f25f8205bd2aca02ba32d0 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Fri, 3 Jul 2026 11:24:00 +0200 Subject: [PATCH 08/23] Correction syntaxe --- plugins/inputs/vsphere/endpoint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index c8ebef9c2804f..ddd405011b5d7 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -1467,7 +1467,7 @@ func populateGlobalFields(objectRef *objectRef) map[string]interface{} { return globalFields } -func (e *endpoint) makeMetricIdentifier(prefix string, metric string) (metricName string, fieldName string) { +func (e *endpoint) makeMetricIdentifier(prefix, metric string) (metricName, fieldName string) { parts := strings.Split(metric, ".") if len(parts) == 1 { return prefix, parts[0] From 14071191cf7a4da132e652adfeda4578b03606f5 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 15 Jul 2026 13:57:05 +0200 Subject: [PATCH 09/23] =?UTF-8?q?Retour=20arri=C3=A8re=20sur=20une=20modif?= =?UTF-8?q?ication=20inutile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/inputs/vsphere/endpoint.go | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index ddd405011b5d7..8780c4c69c418 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -912,25 +912,25 @@ func getDatastores(ctx context.Context, e *endpoint, rf *resourceFilter, propert } func (e *endpoint) loadCustomAttributes(entity mo.ManagedEntity) map[string]string { + if !e.customAttrEnabled { + return make(map[string]string) + } cvs := make(map[string]string) - if e.customAttrEnabled { - for _, v := range entity.CustomValue { - cv, ok := v.(*types.CustomFieldStringValue) - if !ok { - e.parent.Log.Warnf("Metadata for custom field %d not of string type. Skipping", cv.Key) - continue - } - key, ok := e.customFields[cv.Key] - if !ok { - e.parent.Log.Warnf("Metadata for custom field %d not found. Skipping", cv.Key) - continue - } - if e.customAttrFilter.Match(key) { - cvs[key] = cv.Value - } + for _, v := range entity.CustomValue { + cv, ok := v.(*types.CustomFieldStringValue) + if !ok { + e.parent.Log.Warnf("Metadata for custom field %d not of string type. Skipping", cv.Key) + continue + } + key, ok := e.customFields[cv.Key] + if !ok { + e.parent.Log.Warnf("Metadata for custom field %d not found. Skipping", cv.Key) + continue + } + if e.customAttrFilter.Match(key) { + cvs[key] = cv.Value } } - return cvs } From 899b3875f9a1fbb1c45877604cfaa90054ad14ef Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez <91618382+tguenneguez@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:05:46 +0200 Subject: [PATCH 10/23] Symplify code Co-authored-by: Sven Rebhan <36194019+srebhan@users.noreply.github.com> --- plugins/inputs/vsphere/endpoint.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index 8780c4c69c418..8df7af64c82bf 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -936,12 +936,10 @@ func (e *endpoint) loadCustomAttributes(entity mo.ManagedEntity) map[string]stri func (e *endpoint) loadCustomProperties(entity interface{}, propertieInclude []string) map[string]interface{} { cvs := make(map[string]interface{}) - if len(propertieInclude) != 0 { - for _, filtre := range propertieInclude { - if valeur, ok := e.getExtraData(entity, capitalizeAfterDot(filtre)); ok { - key := e.makePropertyIdentifier(filtre) - cvs[key] = valeur - } + for _, filter := range propertiesInclude { + if value, ok := e.getExtraData(entity, capitalizeAfterDot(filter)); ok { + key := e.makePropertyIdentifier(filter) + cvs[key] = value } } return cvs From 588b73b972f03ace7608ff1c8037e5f38a097f7a Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 15 Jul 2026 14:09:16 +0200 Subject: [PATCH 11/23] Correction retour getExtraData --- plugins/inputs/vsphere/endpoint.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index 8780c4c69c418..d19753796bc5d 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -938,7 +938,8 @@ func (e *endpoint) loadCustomProperties(entity interface{}, propertieInclude []s cvs := make(map[string]interface{}) if len(propertieInclude) != 0 { for _, filtre := range propertieInclude { - if valeur, ok := e.getExtraData(entity, capitalizeAfterDot(filtre)); ok { + valeur := e.getExtraData(entity, capitalizeAfterDot(filtre)) + if valeur != nil { key := e.makePropertyIdentifier(filtre) cvs[key] = valeur } @@ -1492,14 +1493,14 @@ func round(x float64) float64 { return t } -func (e *endpoint) getExtraData(entity interface{}, fieldPath string) (interface{}, bool) { +func (e *endpoint) getExtraData(entity interface{}, fieldPath string) interface{} { v := reflect.ValueOf(entity) // Si c'est un pointeur, on dé-référence if v.Kind() == reflect.Ptr { if v.IsNil() { // La valeur est un pointeur nil, on ne peut pas continuer - return nil, false + return nil } v = v.Elem() } @@ -1508,13 +1509,13 @@ func (e *endpoint) getExtraData(entity interface{}, fieldPath string) (interface for _, field := range fields { if v.Kind() != reflect.Struct { e.parent.Log.Warnf("Field %s in %s of %s not struct %s. Skipping", field, fieldPath, reflect.TypeOf(entity), v.Kind()) - return nil, false + return nil } v = v.FieldByName(field) // Si le champ n'existe pas ou n'est pas accessible if !v.IsValid() { e.parent.Log.Warnf("Field %s in %s of %s not valid. Skipping", field, fieldPath, reflect.TypeOf(entity)) - return nil, false + return nil } // Si c'est un pointeur, dé-référencer if v.Kind() == reflect.Ptr { @@ -1524,10 +1525,10 @@ func (e *endpoint) getExtraData(entity interface{}, fieldPath string) (interface // Retourner la valeur sous forme de string if v.IsValid() && v.CanInterface() { - return v, true + return v } e.parent.Log.Warnf("Field %s of %s no interface. Skipping", fieldPath, reflect.TypeOf(entity)) - return nil, false + return nil } func (e *endpoint) makePropertyIdentifier(input string) string { From e617e16cd28a39e6f8b62810107cd15d20514c59 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 15 Jul 2026 14:21:06 +0200 Subject: [PATCH 12/23] Corrige les tests --- plugins/inputs/vsphere/vsphere_test.go | 40 -------------------------- 1 file changed, 40 deletions(-) diff --git a/plugins/inputs/vsphere/vsphere_test.go b/plugins/inputs/vsphere/vsphere_test.go index 3185f6fac95a0..cc64a9f02b95f 100644 --- a/plugins/inputs/vsphere/vsphere_test.go +++ b/plugins/inputs/vsphere/vsphere_test.go @@ -244,10 +244,6 @@ func TestMaxQuery(t *testing.T) { func testLookupVM(ctx context.Context, t *testing.T, f *finder, path string, expected int, expectedName string) { var vm []mo.VirtualMachine - ri := resourceInfo{ - resType: "VirtualMachine", - custoFields: nil, - } err := f.find(ctx, ri, path, &vm) require.NoError(t, err) @@ -274,20 +270,12 @@ func TestFinder(t *testing.T) { f := finder{c} var dc []mo.Datacenter - ri := resourceInfo{ - resType: "Datacenter", - custoFields: nil, - } err = f.find(t.Context(), ri, "/DC0", &dc) require.NoError(t, err) require.Len(t, dc, 1) require.Equal(t, "DC0", dc[0].Name) var host []mo.HostSystem - ri = resourceInfo{ - resType: "HostSystem", - custoFields: nil, - } err = f.find(t.Context(), ri, "/DC0/host/DC0_H0/DC0_H0", &host) require.NoError(t, err) require.Len(t, host, 1) @@ -300,20 +288,12 @@ func TestFinder(t *testing.T) { require.Equal(t, "DC0_C0_H0", host[0].Name) resourcepool := make([]mo.ResourcePool, 0) - ri = resourceInfo{ - resType: "ResourcePool", - custoFields: nil, - } err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/Resources/DC0_C0_RP0", &resourcepool) require.NoError(t, err) require.Len(t, host, 1) require.Equal(t, "DC0_C0_H0", host[0].Name) host = make([]mo.HostSystem, 0) - ri = resourceInfo{ - resType: "HostSystem", - custoFields: nil, - } err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/*", &host) require.NoError(t, err) require.Len(t, host, 3) @@ -333,10 +313,6 @@ func TestFinder(t *testing.T) { testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*/*/*DC*", 4, "") vm = make([]mo.VirtualMachine, 0) - ri = resourceInfo{ - resType: "VirtualMachine", - custoFields: nil, - } err = f.findAll(t.Context(), ri, []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, nil, &vm) require.NoError(t, err) require.Len(t, vm, 4) @@ -409,20 +385,12 @@ func TestFolders(t *testing.T) { f := finder{c} var folder []mo.Folder - ri := resourceInfo{ - resType: "Folder", - custoFields: nil, - } err = f.find(t.Context(), ri, "/F0", &folder) require.NoError(t, err) require.Len(t, folder, 1) require.Equal(t, "F0", folder[0].Name) var dc []mo.Datacenter - ri = resourceInfo{ - resType: "Datacenter", - custoFields: nil, - } err = f.find(t.Context(), ri, "/F0/DC1", &dc) require.NoError(t, err) require.Len(t, dc, 1) @@ -445,10 +413,6 @@ func TestVsanCmmds(t *testing.T) { f := finder{c} var clusters []mo.ClusterComputeResource - ri := resourceInfo{ - resType: "ClusterComputeResource", - custoFields: nil, - } err = f.findAll(t.Context(), ri, []string{"/**"}, nil, &clusters) require.NoError(t, err) @@ -545,10 +509,6 @@ func testCollection(t *testing.T, excludeClusters bool) { client, err := v.endpoints[0].clientFactory.getClient(t.Context()) require.NoError(t, err) hostCache := make(map[string]string) - ri := resourceInfo{ - resType: "HostSystem", - custoFields: nil, - } for _, m := range acc.Metrics { delete(mustHaveMetrics, m.Measurement) From 235438b4ea40b5d428b95166ae0fddee2b00779d Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 15 Jul 2026 14:26:21 +0200 Subject: [PATCH 13/23] Replace resType in resMType --- plugins/inputs/vsphere/finder.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/inputs/vsphere/finder.go b/plugins/inputs/vsphere/finder.go index 7465bb3743a34..bfb98cdeae5c7 100644 --- a/plugins/inputs/vsphere/finder.go +++ b/plugins/inputs/vsphere/finder.go @@ -34,7 +34,7 @@ type resourceFilter struct { // resourceInfo is a utility class grouping a type and relevant parameters. type resourceInfo struct { - resType string + resMType string custoFields []string } @@ -80,7 +80,7 @@ func (f *finder) findResources(ctx context.Context, resourceInfo resourceInfo, p if err != nil { return err } - f.client.log.Debugf("Find(%s, %s) returned %d objects", resourceInfo.resType, path, len(objs)) + f.client.log.Debugf("Find(%s, %s) returned %d objects", resourceInfo.resMType, path, len(objs)) return nil } @@ -114,7 +114,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, objectTypes := ct if isLeaf { - if af, ok := addFields[resourceInfo.resType]; ok { + if af, ok := addFields[resourceInfo.resMType]; ok { fields = append(fields, af...) } fields = append(fields, resourceInfo.custoFields...) @@ -122,12 +122,12 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, if recurse { // Special case: The last token is a recursive wildcard, so we can grab everything // recursively in a single call. - v2, err := m.CreateContainerView(ctx, root, []string{resourceInfo.resType}, true) + v2, err := m.CreateContainerView(ctx, root, []string{resourceInfo.resMType}, true) if err != nil { return err } defer v2.Destroy(ctx) //nolint:errcheck // Ignore the returned error as we cannot do anything about it anyway - err = v2.Retrieve(ctx, []string{resourceInfo.resType}, uniqueFields, &content) + err = v2.Retrieve(ctx, []string{resourceInfo.resMType}, uniqueFields, &content) if err != nil { return err } @@ -136,7 +136,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, } return nil } - objectTypes = []string{resourceInfo.resType} // Only load wanted object type at leaf level + objectTypes = []string{resourceInfo.resMType} // Only load wanted object type at leaf level } err = v.Retrieve(ctx, objectTypes, fields, &content) if err != nil { @@ -154,7 +154,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, continue } - if c.Obj.Type == resourceInfo.resType && isLeaf { + if c.Obj.Type == resourceInfo.resMType && isLeaf { // We found what we're looking for. Consider it a leaf and stop descending objs[c.Obj.String()] = c continue @@ -170,7 +170,7 @@ func (f *finder) descend(ctx context.Context, root types.ManagedObjectReference, // Rerun the entire level as a leaf. This is needed since all properties aren't loaded // when we're processing non-leaf nodes. if pos == len(tokens)-2 { - if c.Obj.Type == resourceInfo.resType { + if c.Obj.Type == resourceInfo.resMType { rerunAsLeaf = true continue } @@ -234,7 +234,7 @@ func objectContentToTypedArray(objs map[string]types.ObjectContent, dst interfac // findAll finds all resources matching the paths that were specified upon creation of the resourceFilter. func (r *resourceFilter) findAll(ctx context.Context, dst interface{}) error { resourceInfo := resourceInfo{ - resType: r.resType, + resMType: r.resType, custoFields: r.custoFields} return r.finder.findAll(ctx, resourceInfo, r.paths, r.excludePaths, dst) From 5beb4b6307a417995626aa784a61742a2808fc0b Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 15 Jul 2026 14:29:56 +0200 Subject: [PATCH 14/23] Translate comments --- plugins/inputs/vsphere/endpoint.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index e068238b3a5bd..c43ad34712735 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -1493,10 +1493,10 @@ func round(x float64) float64 { func (e *endpoint) getExtraData(entity interface{}, fieldPath string) interface{} { v := reflect.ValueOf(entity) - // Si c'est un pointeur, on dé-référence + // If it's a pointer, we dereference it. if v.Kind() == reflect.Ptr { if v.IsNil() { - // La valeur est un pointeur nil, on ne peut pas continuer + // The value is a nil pointer; cannot continue. return nil } v = v.Elem() @@ -1509,18 +1509,18 @@ func (e *endpoint) getExtraData(entity interface{}, fieldPath string) interface{ return nil } v = v.FieldByName(field) - // Si le champ n'existe pas ou n'est pas accessible + // If the field does not exist or is not accessible if !v.IsValid() { e.parent.Log.Warnf("Field %s in %s of %s not valid. Skipping", field, fieldPath, reflect.TypeOf(entity)) return nil } - // Si c'est un pointeur, dé-référencer + // If it is a pointer, dereference it. if v.Kind() == reflect.Ptr { v = v.Elem() } } - // Retourner la valeur sous forme de string + // Return the value as a string. if v.IsValid() && v.CanInterface() { return v } From 22ae6ac4c913a4a780fb3f96febe5aa75d84d4d0 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 15 Jul 2026 14:32:45 +0200 Subject: [PATCH 15/23] Corrige syntaxe --- plugins/inputs/vsphere/endpoint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index c43ad34712735..f58abb31316ee 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -1529,7 +1529,7 @@ func (e *endpoint) getExtraData(entity interface{}, fieldPath string) interface{ } func (e *endpoint) makePropertyIdentifier(input string) string { - return strings..ReplaceAll(input, '.', e.parent.Separator) + return strings.ReplaceAll(input, '.', e.parent.Separator) } func capitalizeAfterDot(input string) string { From 9ad4016ad59b50570a6e70edffd0d1f4952d4c2c Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 15 Jul 2026 14:55:05 +0200 Subject: [PATCH 16/23] Modify syntaxe --- plugins/inputs/vsphere/endpoint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index f58abb31316ee..7e1f4040649b7 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -934,7 +934,7 @@ func (e *endpoint) loadCustomAttributes(entity mo.ManagedEntity) map[string]stri return cvs } -func (e *endpoint) loadCustomProperties(entity interface{}, propertieInclude []string) map[string]interface{} { +func (e *endpoint) loadCustomProperties(entity interface{}, propertiesInclude []string) map[string]interface{} { cvs := make(map[string]interface{}) for _, filter := range propertiesInclude { if value, ok := e.getExtraData(entity, capitalizeAfterDot(filter)); ok { From e06c9c86dc1b8523b6c5d63db1e0b61cf1ed4244 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 15 Jul 2026 16:22:49 +0200 Subject: [PATCH 17/23] Correction syntaxe --- plugins/inputs/vsphere/endpoint.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index 7e1f4040649b7..bfaaebf70f7fd 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -937,7 +937,8 @@ func (e *endpoint) loadCustomAttributes(entity mo.ManagedEntity) map[string]stri func (e *endpoint) loadCustomProperties(entity interface{}, propertiesInclude []string) map[string]interface{} { cvs := make(map[string]interface{}) for _, filter := range propertiesInclude { - if value, ok := e.getExtraData(entity, capitalizeAfterDot(filter)); ok { + value := e.getExtraData(entity, capitalizeAfterDot(filter)) + if value != nil { key := e.makePropertyIdentifier(filter) cvs[key] = value } From 48a8df7052a33bcc96027963ded537e6233b1a71 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Wed, 15 Jul 2026 17:35:30 +0200 Subject: [PATCH 18/23] Correction syntaxe --- plugins/inputs/vsphere/endpoint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index bfaaebf70f7fd..d4de8a8a9c769 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -1530,7 +1530,7 @@ func (e *endpoint) getExtraData(entity interface{}, fieldPath string) interface{ } func (e *endpoint) makePropertyIdentifier(input string) string { - return strings.ReplaceAll(input, '.', e.parent.Separator) + return strings.ReplaceAll(input, ".", e.parent.Separator) } func capitalizeAfterDot(input string) string { From 98ba92c5b8103eef652cee8d68c17a7c211ad790 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Thu, 16 Jul 2026 09:23:33 +0200 Subject: [PATCH 19/23] Retire custoFields: nil, --- plugins/inputs/vsphere/vsphere_test.go | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/plugins/inputs/vsphere/vsphere_test.go b/plugins/inputs/vsphere/vsphere_test.go index cc64a9f02b95f..4e259412857dd 100644 --- a/plugins/inputs/vsphere/vsphere_test.go +++ b/plugins/inputs/vsphere/vsphere_test.go @@ -244,6 +244,9 @@ func TestMaxQuery(t *testing.T) { func testLookupVM(ctx context.Context, t *testing.T, f *finder, path string, expected int, expectedName string) { var vm []mo.VirtualMachine + ri := resourceInfo{ + resType: "VirtualMachine", + } err := f.find(ctx, ri, path, &vm) require.NoError(t, err) @@ -270,12 +273,18 @@ func TestFinder(t *testing.T) { f := finder{c} var dc []mo.Datacenter + ri := resourceInfo{ + resType: "Datacenter", + } err = f.find(t.Context(), ri, "/DC0", &dc) require.NoError(t, err) require.Len(t, dc, 1) require.Equal(t, "DC0", dc[0].Name) var host []mo.HostSystem + ri = resourceInfo{ + resType: "HostSystem", + } err = f.find(t.Context(), ri, "/DC0/host/DC0_H0/DC0_H0", &host) require.NoError(t, err) require.Len(t, host, 1) @@ -288,12 +297,18 @@ func TestFinder(t *testing.T) { require.Equal(t, "DC0_C0_H0", host[0].Name) resourcepool := make([]mo.ResourcePool, 0) + ri = resourceInfo{ + resType: "ResourcePool", + } err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/Resources/DC0_C0_RP0", &resourcepool) require.NoError(t, err) require.Len(t, host, 1) require.Equal(t, "DC0_C0_H0", host[0].Name) host = make([]mo.HostSystem, 0) + ri = resourceInfo{ + resType: "HostSystem", + } err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/*", &host) require.NoError(t, err) require.Len(t, host, 3) @@ -313,6 +328,9 @@ func TestFinder(t *testing.T) { testLookupVM(t.Context(), t, &f, "/*/host/**/*DC*/*/*DC*", 4, "") vm = make([]mo.VirtualMachine, 0) + ri = resourceInfo{ + resType: "VirtualMachine", + } err = f.findAll(t.Context(), ri, []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, nil, &vm) require.NoError(t, err) require.Len(t, vm, 4) @@ -385,12 +403,18 @@ func TestFolders(t *testing.T) { f := finder{c} var folder []mo.Folder + ri := resourceInfo{ + resType: "Folder", + } err = f.find(t.Context(), ri, "/F0", &folder) require.NoError(t, err) require.Len(t, folder, 1) require.Equal(t, "F0", folder[0].Name) var dc []mo.Datacenter + ri = resourceInfo{ + resType: "Datacenter", + } err = f.find(t.Context(), ri, "/F0/DC1", &dc) require.NoError(t, err) require.Len(t, dc, 1) @@ -413,6 +437,9 @@ func TestVsanCmmds(t *testing.T) { f := finder{c} var clusters []mo.ClusterComputeResource + ri := resourceInfo{ + resType: "ClusterComputeResource", + } err = f.findAll(t.Context(), ri, []string{"/**"}, nil, &clusters) require.NoError(t, err) @@ -509,6 +536,9 @@ func testCollection(t *testing.T, excludeClusters bool) { client, err := v.endpoints[0].clientFactory.getClient(t.Context()) require.NoError(t, err) hostCache := make(map[string]string) + ri := resourceInfo{ + resType: "HostSystem", + } for _, m := range acc.Metrics { delete(mustHaveMetrics, m.Measurement) From 705997e3ce9f9cacfb2777cf14edc7fe3b0d6010 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Thu, 16 Jul 2026 09:25:37 +0200 Subject: [PATCH 20/23] Corrige filtres --- plugins/inputs/vsphere/vsphere_test.go | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/inputs/vsphere/vsphere_test.go b/plugins/inputs/vsphere/vsphere_test.go index 4e259412857dd..6d53569436d7e 100644 --- a/plugins/inputs/vsphere/vsphere_test.go +++ b/plugins/inputs/vsphere/vsphere_test.go @@ -245,7 +245,7 @@ func TestMaxQuery(t *testing.T) { func testLookupVM(ctx context.Context, t *testing.T, f *finder, path string, expected int, expectedName string) { var vm []mo.VirtualMachine ri := resourceInfo{ - resType: "VirtualMachine", + resMType: "VirtualMachine", } err := f.find(ctx, ri, path, &vm) @@ -274,7 +274,7 @@ func TestFinder(t *testing.T) { var dc []mo.Datacenter ri := resourceInfo{ - resType: "Datacenter", + resMType: "Datacenter", } err = f.find(t.Context(), ri, "/DC0", &dc) require.NoError(t, err) @@ -283,7 +283,7 @@ func TestFinder(t *testing.T) { var host []mo.HostSystem ri = resourceInfo{ - resType: "HostSystem", + resMType: "HostSystem", } err = f.find(t.Context(), ri, "/DC0/host/DC0_H0/DC0_H0", &host) require.NoError(t, err) @@ -298,7 +298,7 @@ func TestFinder(t *testing.T) { resourcepool := make([]mo.ResourcePool, 0) ri = resourceInfo{ - resType: "ResourcePool", + resMType: "ResourcePool", } err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/Resources/DC0_C0_RP0", &resourcepool) require.NoError(t, err) @@ -307,7 +307,7 @@ func TestFinder(t *testing.T) { host = make([]mo.HostSystem, 0) ri = resourceInfo{ - resType: "HostSystem", + resMType: "HostSystem", } err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/*", &host) require.NoError(t, err) @@ -329,7 +329,7 @@ func TestFinder(t *testing.T) { vm = make([]mo.VirtualMachine, 0) ri = resourceInfo{ - resType: "VirtualMachine", + resMType: "VirtualMachine", } err = f.findAll(t.Context(), ri, []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, nil, &vm) require.NoError(t, err) @@ -339,7 +339,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, excludePaths: []string{"/DC0/vm/DC0_H0_VM0"}, - resType: "VirtualMachine", + resMType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) @@ -349,7 +349,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, excludePaths: []string{"/**"}, - resType: "VirtualMachine", + resMType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) @@ -359,7 +359,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/**"}, excludePaths: []string{"/**"}, - resType: "VirtualMachine", + resMType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) @@ -369,7 +369,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/**"}, excludePaths: []string{"/this won't match anything"}, - resType: "VirtualMachine", + resMType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) @@ -379,7 +379,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/**"}, excludePaths: []string{"/**/*VM0"}, - resType: "VirtualMachine", + resMType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) @@ -404,7 +404,7 @@ func TestFolders(t *testing.T) { var folder []mo.Folder ri := resourceInfo{ - resType: "Folder", + resMType: "Folder", } err = f.find(t.Context(), ri, "/F0", &folder) require.NoError(t, err) @@ -413,7 +413,7 @@ func TestFolders(t *testing.T) { var dc []mo.Datacenter ri = resourceInfo{ - resType: "Datacenter", + resMType: "Datacenter", } err = f.find(t.Context(), ri, "/F0/DC1", &dc) require.NoError(t, err) @@ -438,7 +438,7 @@ func TestVsanCmmds(t *testing.T) { f := finder{c} var clusters []mo.ClusterComputeResource ri := resourceInfo{ - resType: "ClusterComputeResource", + resMType: "ClusterComputeResource", } err = f.findAll(t.Context(), ri, []string{"/**"}, nil, &clusters) require.NoError(t, err) @@ -537,7 +537,7 @@ func testCollection(t *testing.T, excludeClusters bool) { require.NoError(t, err) hostCache := make(map[string]string) ri := resourceInfo{ - resType: "HostSystem", + resMType: "HostSystem", } for _, m := range acc.Metrics { delete(mustHaveMetrics, m.Measurement) From e050ea7b200c2ce4c92d0372586cf4e963404316 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Thu, 16 Jul 2026 09:29:49 +0200 Subject: [PATCH 21/23] Corrige les filtres --- plugins/inputs/vsphere/vsphere_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/inputs/vsphere/vsphere_test.go b/plugins/inputs/vsphere/vsphere_test.go index 6d53569436d7e..0ac4bd2c4d661 100644 --- a/plugins/inputs/vsphere/vsphere_test.go +++ b/plugins/inputs/vsphere/vsphere_test.go @@ -339,7 +339,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, excludePaths: []string{"/DC0/vm/DC0_H0_VM0"}, - resMType: "VirtualMachine", + resType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) @@ -349,7 +349,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, excludePaths: []string{"/**"}, - resMType: "VirtualMachine", + resType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) @@ -359,7 +359,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/**"}, excludePaths: []string{"/**"}, - resMType: "VirtualMachine", + resType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) @@ -369,7 +369,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/**"}, excludePaths: []string{"/this won't match anything"}, - resMType: "VirtualMachine", + resType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) @@ -379,7 +379,7 @@ func TestFinder(t *testing.T) { finder: &f, paths: []string{"/**"}, excludePaths: []string{"/**/*VM0"}, - resMType: "VirtualMachine", + resType: "VirtualMachine", } vm = make([]mo.VirtualMachine, 0) require.NoError(t, rf.findAll(t.Context(), &vm)) From 5d614ec187062553d57aea9aefe2edff983d8966 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Thu, 16 Jul 2026 14:56:45 +0200 Subject: [PATCH 22/23] Corrige syntaxe --- plugins/inputs/vsphere/vsphere_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/inputs/vsphere/vsphere_test.go b/plugins/inputs/vsphere/vsphere_test.go index 0ac4bd2c4d661..67aadd3cb1a31 100644 --- a/plugins/inputs/vsphere/vsphere_test.go +++ b/plugins/inputs/vsphere/vsphere_test.go @@ -245,7 +245,7 @@ func TestMaxQuery(t *testing.T) { func testLookupVM(ctx context.Context, t *testing.T, f *finder, path string, expected int, expectedName string) { var vm []mo.VirtualMachine ri := resourceInfo{ - resMType: "VirtualMachine", + resMType: "VirtualMachine", } err := f.find(ctx, ri, path, &vm) @@ -274,7 +274,7 @@ func TestFinder(t *testing.T) { var dc []mo.Datacenter ri := resourceInfo{ - resMType: "Datacenter", + resMType: "Datacenter", } err = f.find(t.Context(), ri, "/DC0", &dc) require.NoError(t, err) @@ -283,7 +283,7 @@ func TestFinder(t *testing.T) { var host []mo.HostSystem ri = resourceInfo{ - resMType: "HostSystem", + resMType: "HostSystem", } err = f.find(t.Context(), ri, "/DC0/host/DC0_H0/DC0_H0", &host) require.NoError(t, err) @@ -298,7 +298,7 @@ func TestFinder(t *testing.T) { resourcepool := make([]mo.ResourcePool, 0) ri = resourceInfo{ - resMType: "ResourcePool", + resMType: "ResourcePool", } err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/Resources/DC0_C0_RP0", &resourcepool) require.NoError(t, err) @@ -307,7 +307,7 @@ func TestFinder(t *testing.T) { host = make([]mo.HostSystem, 0) ri = resourceInfo{ - resMType: "HostSystem", + resMType: "HostSystem", } err = f.find(t.Context(), ri, "/DC0/host/DC0_C0/*", &host) require.NoError(t, err) @@ -329,7 +329,7 @@ func TestFinder(t *testing.T) { vm = make([]mo.VirtualMachine, 0) ri = resourceInfo{ - resMType: "VirtualMachine", + resMType: "VirtualMachine", } err = f.findAll(t.Context(), ri, []string{"/DC0/vm/DC0_H0*", "/DC0/vm/DC0_C0*"}, nil, &vm) require.NoError(t, err) @@ -404,7 +404,7 @@ func TestFolders(t *testing.T) { var folder []mo.Folder ri := resourceInfo{ - resMType: "Folder", + resMType: "Folder", } err = f.find(t.Context(), ri, "/F0", &folder) require.NoError(t, err) @@ -413,7 +413,7 @@ func TestFolders(t *testing.T) { var dc []mo.Datacenter ri = resourceInfo{ - resMType: "Datacenter", + resMType: "Datacenter", } err = f.find(t.Context(), ri, "/F0/DC1", &dc) require.NoError(t, err) @@ -438,7 +438,7 @@ func TestVsanCmmds(t *testing.T) { f := finder{c} var clusters []mo.ClusterComputeResource ri := resourceInfo{ - resMType: "ClusterComputeResource", + resMType: "ClusterComputeResource", } err = f.findAll(t.Context(), ri, []string{"/**"}, nil, &clusters) require.NoError(t, err) @@ -537,7 +537,7 @@ func testCollection(t *testing.T, excludeClusters bool) { require.NoError(t, err) hostCache := make(map[string]string) ri := resourceInfo{ - resMType: "HostSystem", + resMType: "HostSystem", } for _, m := range acc.Metrics { delete(mustHaveMetrics, m.Measurement) From 29005b496c383f88666982479bbbbbaa0ca1ea43 Mon Sep 17 00:00:00 2001 From: Thomas Guenneguez Date: Thu, 16 Jul 2026 15:17:39 +0200 Subject: [PATCH 23/23] Rename var --- plugins/inputs/vsphere/endpoint.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/inputs/vsphere/endpoint.go b/plugins/inputs/vsphere/endpoint.go index d4de8a8a9c769..b1eecf6b399a4 100644 --- a/plugins/inputs/vsphere/endpoint.go +++ b/plugins/inputs/vsphere/endpoint.go @@ -936,10 +936,10 @@ func (e *endpoint) loadCustomAttributes(entity mo.ManagedEntity) map[string]stri func (e *endpoint) loadCustomProperties(entity interface{}, propertiesInclude []string) map[string]interface{} { cvs := make(map[string]interface{}) - for _, filter := range propertiesInclude { - value := e.getExtraData(entity, capitalizeAfterDot(filter)) + for _, property := range propertiesInclude { + value := e.getExtraData(entity, capitalizeAfterDot(property)) if value != nil { - key := e.makePropertyIdentifier(filter) + key := e.makePropertyIdentifier(property) cvs[key] = value } }