mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves https://fleetdm.slack.com/archives/C03C41L5YEL/p1761232938314509, but also https://github.com/fleetdm/fleet/issues/34363, as it would have been stupid to only add IDP_USERNAME (to allow for an email) when they are so close together and no extra effort to support the rest. Also does another fix to ensure fleet variables are correctly ordered by longest name to shortest, to ensure variables that are used in longer format (USERNAME, USERNAME_LOCAL_PART) that the LOCAL_PART one gets processed first. # Checklist for submitter If some of the following don't apply, delete the relevant line. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually
111 lines
3.8 KiB
Go
111 lines
3.8 KiB
Go
// Package variables provides functionality for handling Fleet variables,
|
|
// which are template placeholders that can be substituted with actual values
|
|
// in various contexts.
|
|
package variables
|
|
|
|
import (
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// fleetVariableRegex matches Fleet variables in content.
|
|
// It supports two formats:
|
|
// - $FLEET_VAR_NAME - without braces
|
|
// - ${FLEET_VAR_NAME} - with braces
|
|
//
|
|
// The regex captures the variable name (without the FLEET_VAR_ prefix) in named groups.
|
|
var fleetVariableRegex = regexp.MustCompile(`(\$FLEET_VAR_(?P<name1>\w+))|(\${FLEET_VAR_(?P<name2>\w+)})`)
|
|
|
|
// ProfileDataVariableRegex matches variables present in <data> section of Apple profile, which may cause validation issues.
|
|
// This is specific to DigiCert certificate data variables.
|
|
var ProfileDataVariableRegex = regexp.MustCompile(`(\$FLEET_VAR_DIGICERT_DATA_(?P<name1>\w+))|(\${FLEET_VAR_DIGICERT_DATA_(?P<name2>\w+)})`)
|
|
|
|
// Find finds all Fleet variables in the given content and returns them as a map
|
|
// without the FLEET_VAR_ prefix. Returns nil if no variables are found.
|
|
//
|
|
// For example, if the content contains "$FLEET_VAR_HOST_UUID" and "${FLEET_VAR_HOST_EMAIL}",
|
|
// this function will return map[string]struct{}{"HOST_UUID": {}, "HOST_EMAIL": {}}.
|
|
func Find(contents string) []string {
|
|
resultSlice := FindKeepDuplicates(contents)
|
|
if len(resultSlice) == 0 {
|
|
return nil
|
|
}
|
|
return Dedupe(resultSlice)
|
|
}
|
|
|
|
// FindKeepDuplicates finds all Fleet variables in the given content and returns them
|
|
// as a slice without the FLEET_VAR_ prefix. Duplicates are preserved in the result.
|
|
//
|
|
// This is useful when you need to know the order or frequency of variable occurrences.
|
|
func FindKeepDuplicates(contents string) []string {
|
|
var result []string
|
|
matches := fleetVariableRegex.FindAllStringSubmatch(contents, -1)
|
|
if len(matches) == 0 {
|
|
return nil
|
|
}
|
|
|
|
nameToIndex := make(map[string]int, 2)
|
|
for i, name := range fleetVariableRegex.SubexpNames() {
|
|
if name == "" {
|
|
continue
|
|
}
|
|
nameToIndex[name] = i
|
|
}
|
|
|
|
for _, match := range matches {
|
|
for _, i := range nameToIndex {
|
|
if match[i] != "" {
|
|
result = append(result, match[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// sort result array by length descending, to ensure longer variables are processed first
|
|
sortedResults := make([]string, len(result))
|
|
copy(sortedResults, result)
|
|
sort.Slice(sortedResults, func(i, j int) bool {
|
|
return len(sortedResults[i]) > len(sortedResults[j])
|
|
})
|
|
return sortedResults
|
|
}
|
|
|
|
// Dedupe removes duplicates from the slice and returns a slice to keep order of variables
|
|
func Dedupe(varsWithDupes []string) []string {
|
|
if len(varsWithDupes) == 0 {
|
|
return []string{}
|
|
}
|
|
|
|
seenMap := make(map[string]bool, len(varsWithDupes))
|
|
var result []string
|
|
for _, v := range varsWithDupes {
|
|
if !seenMap[v] {
|
|
result = append(result, v)
|
|
seenMap[v] = true
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Replace replaces all occurrences of a specific Fleet variable with the given value.
|
|
// The variableName should be provided without the FLEET_VAR_ prefix.
|
|
// This function replaces both braced and non-braced versions of the variable.
|
|
//
|
|
// For example, Replace(content, "HOST_UUID", "123-456") will replace both
|
|
// $FLEET_VAR_HOST_UUID and ${FLEET_VAR_HOST_UUID} with "123-456".
|
|
func Replace(contents string, variableName string, value string) string {
|
|
// Replace both braced and non-braced versions
|
|
result := strings.ReplaceAll(contents, "$FLEET_VAR_"+variableName, value)
|
|
result = strings.ReplaceAll(result, "${FLEET_VAR_"+variableName+"}", value)
|
|
return result
|
|
}
|
|
|
|
// Contains checks if the given content contains any Fleet variables.
|
|
func Contains(contents string) bool {
|
|
return fleetVariableRegex.MatchString(contents)
|
|
}
|
|
|
|
// ContainsBytes checks if the given content contains any Fleet variables (bytes version).
|
|
func ContainsBytes(contents []byte) bool {
|
|
return fleetVariableRegex.Match(contents)
|
|
}
|