Skip to content

Commit

Permalink
feat!: add host group concept to inventory, support globs + regex
Browse files Browse the repository at this point in the history
Lots of changes:

- new `Group` type for new inventory component
- new prometheus metrics with the component
- `Store` interface functions that deal with enrollment have been
  updated to account for groups, as well
- Manager has been updated to support keeping a list of group(s)/host
  variable file paths to source on reload, to allow for sourcing
variables from `variables` files in any/all groups and/or from explicit
host configs
- `inventory.GetVariablesForSelf()` has been updated to return a slice
  of strings containing the paths to `variables` files relevant to the
current host
- add new func `filterDuplicateRoles()` to remove duplicate roles that
  could be assigned across multiple groups a host may be a member to
- various test inventory updates to better utilize `group` configs
  • Loading branch information
tjhop committed Jul 21, 2023
1 parent 36bdb3f commit 68a99c1
Show file tree
Hide file tree
Showing 21 changed files with 417 additions and 70 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.20

require (
github.com/dominikbraun/graph v0.22.3
github.com/gobwas/glob v0.2.3
github.com/hashicorp/go-sockaddr v1.0.2
github.com/moby/moby v24.0.1+incompatible
github.com/oklog/run v1.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down
216 changes: 216 additions & 0 deletions internal/inventory/group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package inventory

import (
"context"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/tjhop/mango/internal/utils"

glob_util "github.com/gobwas/glob"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)

// Group contains fields that represent a given group of groups in the inventory.
// - id: string idenitfying the group
// - globs: a slice of glob patterns to match against the instance's hostname
// - patterns: a slice of regex patterns to match against the instance's hostname
// - roles: a slice of roles that are applied to this host
// - modules: a slice of ad-hoc module names applied to this host
// - variables: path to the variables file for this group, if present
type Group struct {
id string
globs []string
patterns []string
modules []string
roles []string
variables string
}

// String is a stringer to return the group ID
func (g Group) String() string { return g.id }

// ParseGroups looks for groups in the inventory's `groups/` folder. It looks for
// folders within this directory, and then parses each directory into a Group struct.
// Each Group folder may contain a file `glob` containing a newline separated
// list of glob matches, and a `regex` file containing regular expression
// patterns for comparing groupnames.
func (i *Inventory) ParseGroups(ctx context.Context) error {
commonLabels := prometheus.Labels{
"inventory": i.inventoryPath,
"component": "groups",
}

path := filepath.Join(i.inventoryPath, "groups")
groupDirs, err := utils.GetFilesInDirectory(path)
if err != nil {
log.WithFields(log.Fields{
"path": path,
"error": err,
}).Error("Failed to get files in directory")

// inventory counts haven't been altered, no need to update here
metricInventoryReloadFailedTotal.With(commonLabels).Inc()

return err
}

var groups []Group

for _, groupDir := range groupDirs {
if groupDir.IsDir() {
groupPath := filepath.Join(path, groupDir.Name())
groupFiles, err := utils.GetFilesInDirectory(groupPath)
if err != nil {
log.WithFields(log.Fields{
"path": groupPath,
"error": err,
}).Error("Failed to parse group files")

// inventory counts haven't been altered, no need to update here
metricInventoryReloadFailedTotal.With(commonLabels).Inc()

return err
}

group := Group{id: groupDir.Name()}

for _, groupFile := range groupFiles {
if !groupFile.IsDir() && !strings.HasPrefix(groupFile.Name(), ".") {
fileName := groupFile.Name()
switch fileName {
case "glob":
var globs []string
globPath := filepath.Join(groupPath, "glob")
lines := utils.ReadFileLines(globPath)

for line := range lines {
if line.Err != nil {
log.WithFields(log.Fields{
"path": globPath,
"error": line.Err,
}).Error("Failed to read globs for group")
} else {
globs = append(globs, line.Text)
}
}

group.globs = globs
case "regex":
var patterns []string
patternPath := filepath.Join(groupPath, "regex")
lines := utils.ReadFileLines(patternPath)

for line := range lines {
if line.Err != nil {
log.WithFields(log.Fields{
"path": patternPath,
"error": line.Err,
}).Error("Failed to read regexs for group")
} else {
patterns = append(patterns, line.Text)
}
}

group.patterns = patterns
case "roles":
var roles []string
rolePath := filepath.Join(groupPath, "roles")
lines := utils.ReadFileLines(rolePath)

for line := range lines {
if line.Err != nil {
log.WithFields(log.Fields{
"path": rolePath,
"error": line.Err,
}).Error("Failed to read roles for group")
} else {
roles = append(roles, line.Text)
}
}

group.roles = roles
case "modules":
var mods []string
modPath := filepath.Join(groupPath, "modules")
lines := utils.ReadFileLines(modPath)

for line := range lines {
if line.Err != nil {
log.WithFields(log.Fields{
"path": modPath,
"error": line.Err,
}).Error("Failed to read modules for group")
} else {
mods = append(mods, line.Text)
}
}

group.modules = mods
case "variables":
group.variables = filepath.Join(groupPath, "variables")
default:
log.WithFields(log.Fields{
"file": fileName,
}).Debug("Not sure what to do with this file, so skipping it.")
}
}
}

groups = append(groups, group)
}
}

i.groups = groups
metricInventory.With(commonLabels).Set(float64(len(i.groups)))
groupMatches := 0
for _, group := range i.groups {
if group.IsHostEnrolled(i.hostname) {
groupMatches++
}
}
metricInventoryApplicable.With(commonLabels).Set(float64(groupMatches))
metricInventoryReloadSeconds.With(commonLabels).Set(float64(time.Now().Unix()))
metricInventoryReloadTotal.With(commonLabels).Inc()

return nil
}

func (g Group) MatchGlobs(hostname string) int {
matched := 0

for _, globPattern := range g.globs {
glob := glob_util.MustCompile(globPattern)
if glob.Match(hostname) {
matched++
continue
}
}

return matched
}

func (g Group) MatchPatterns(hostname string) int {
matched := 0

for _, pattern := range g.patterns {
validPattern := regexp.MustCompile(pattern)
if validPattern.MatchString(hostname) {
matched++
continue
}
}

return matched
}

func (g Group) MatchAll(hostname string) int {
return g.MatchGlobs(hostname) + g.MatchPatterns(hostname)
}

func (g Group) IsHostEnrolled(hostname string) bool {
return g.MatchAll(hostname) > 0
}
6 changes: 0 additions & 6 deletions internal/inventory/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ type Host struct {
variables string
}

// GetVariables returns a VariableMap of variables
// assigned to this host
func (h Host) GetVariables() string {
return h.variables
}

// String is a stringer to return the host ID
func (h Host) String() string { return h.id }

Expand Down
Loading

0 comments on commit 68a99c1

Please sign in to comment.