iop/scripts/inventory-query/provider_query.go

172 lines
5.2 KiB
Go

package main
import "fmt"
// providerCollector owns selector, matches, and seen for provider traversal.
type providerCollector struct {
selector string
matches []match
seen map[string]bool
}
// newProviderCollector returns a collector ready to find selector matches.
func newProviderCollector(selector string) *providerCollector {
return &providerCollector{
selector: selector,
seen: make(map[string]bool),
}
}
// addEntryMatch records a match when the entry value or key equals the selector.
// isKeyMatch reports whether the map key itself equals the selector.
// item is the entry value, which may be a scalar, map with id/provider, or other.
func (c *providerCollector) addEntryMatch(isKeyMatch bool, item interface{}, entryPath string) {
if isKeyMatch {
c.matches = append(c.matches, match{Path: entryPath, Value: item})
c.seen[entryPath] = true
return
}
switch v := item.(type) {
case string:
if v == c.selector && !c.seen[entryPath] {
c.matches = append(c.matches, match{Path: entryPath, Value: v})
c.seen[entryPath] = true
}
case map[string]interface{}:
if id, ok := v["id"].(string); ok && id == c.selector {
if !c.seen[entryPath] {
c.matches = append(c.matches, match{Path: entryPath, Value: v})
c.seen[entryPath] = true
}
} else if provider, ok := v["provider"].(string); ok && provider == c.selector {
if !c.seen[entryPath] {
c.matches = append(c.matches, match{Path: entryPath, Value: v})
c.seen[entryPath] = true
}
}
}
}
// collect walks val and records provider matches, recursing into nested shapes.
func (c *providerCollector) collect(val interface{}, path string) {
switch v := val.(type) {
case map[string]interface{}:
c.checkProviderField(v, path)
c.checkProvidersField(v, path)
c.checkDirectProvidersField(v, path)
c.recurseNonProviderFields(v, path)
case []interface{}:
for i, item := range v {
c.collect(item, fmt.Sprintf("%s[%d]", path, i))
}
}
}
// checkProviderField inspects the singular "provider" key for selector matches.
func (c *providerCollector) checkProviderField(obj map[string]interface{}, basePath string) {
provVal, ok := obj["provider"]
if !ok {
return
}
childPath := joinPath(basePath, "provider")
switch p := provVal.(type) {
case string:
if p == c.selector && !c.seen[childPath] {
c.matches = append(c.matches, match{Path: childPath, Value: p})
c.seen[childPath] = true
}
case map[string]interface{}:
if id, ok := p["id"].(string); ok && id == c.selector {
if !c.seen[childPath] {
c.matches = append(c.matches, match{Path: childPath, Value: p})
c.seen[childPath] = true
}
} else if id, ok := p["provider"].(string); ok && id == c.selector {
if !c.seen[childPath] {
c.matches = append(c.matches, match{Path: childPath, Value: p})
c.seen[childPath] = true
}
}
}
}
// checkProvidersField inspects the "providers" key (map or list) for selector matches.
func (c *providerCollector) checkProvidersField(obj map[string]interface{}, basePath string) {
provsVal, ok := obj["providers"]
if !ok {
return
}
childPath := joinPath(basePath, "providers")
switch p := provsVal.(type) {
case map[string]interface{}:
for key, item := range p {
entryPath := joinPath(childPath, key)
c.addEntryMatch(key == c.selector, item, entryPath)
c.collect(item, entryPath)
}
case []interface{}:
for i, item := range p {
pPath := fmt.Sprintf("%s[%d]", childPath, i)
if s, ok := item.(string); ok && s == c.selector {
if !c.seen[pPath] {
c.matches = append(c.matches, match{Path: pPath, Value: s})
c.seen[pPath] = true
}
} else if m, ok := item.(map[string]interface{}); ok {
if id, ok := m["id"].(string); ok && id == c.selector {
if !c.seen[pPath] {
c.matches = append(c.matches, match{Path: pPath, Value: m})
c.seen[pPath] = true
}
} else if id, ok := m["provider"].(string); ok && id == c.selector {
if !c.seen[pPath] {
c.matches = append(c.matches, match{Path: pPath, Value: m})
c.seen[pPath] = true
}
}
}
c.collect(item, pPath)
}
}
}
// checkDirectProvidersField inspects the "direct_providers" list for selector matches.
func (c *providerCollector) checkDirectProvidersField(obj map[string]interface{}, basePath string) {
dpsVal, ok := obj["direct_providers"]
if !ok {
return
}
p, ok := dpsVal.([]interface{})
if !ok {
return
}
childPath := joinPath(basePath, "direct_providers")
for i, item := range p {
pPath := fmt.Sprintf("%s[%d]", childPath, i)
if s, ok := item.(string); ok && s == c.selector {
if !c.seen[pPath] {
c.matches = append(c.matches, match{Path: pPath, Value: s})
c.seen[pPath] = true
}
} else if m, ok := item.(map[string]interface{}); ok {
if id, ok := m["id"].(string); ok && id == c.selector {
if !c.seen[pPath] {
c.matches = append(c.matches, match{Path: pPath, Value: m})
c.seen[pPath] = true
}
}
}
c.collect(item, pPath)
}
}
// recurseNonProviderFields continues traversal into fields that are not provider-shaped.
func (c *providerCollector) recurseNonProviderFields(obj map[string]interface{}, basePath string) {
for k, valItem := range obj {
if k == "provider" || k == "providers" || k == "direct_providers" {
continue
}
c.collect(valItem, joinPath(basePath, k))
}
}