merge main

This commit is contained in:
Carlo DiCelico
2026-07-15 19:17:29 -04:00
129 changed files with 6705 additions and 492 deletions
+59 -4
View File
@@ -422,6 +422,10 @@ type GitOps struct {
Labels []*fleet.LabelSpec
LabelChangesSummary LabelChangesSummary
// CustomHostVitals are the custom host vital definitions (names only; per-host
// values are never set via GitOps). Global-only: cannot be set on a team/fleet file.
CustomHostVitals []fleet.CustomHostVital
// Software is only allowed on teams, not on global config.
Software GitOpsSoftware
// FleetSecrets is a map of secret names to their values, extracted from FLEET_SECRET_ environment variables used in profiles and scripts.
@@ -433,6 +437,15 @@ type GitOps struct {
SoftwarePresent bool
// SecretsPresent indicates that the `secrets:` key was explicitly present in the YAML file.
SecretsPresent bool
// CustomHostVitalsPresent indicates that the `custom_host_vitals:` key was explicitly present in the YAML file.
CustomHostVitalsPresent bool
}
// GitOpsCustomHostVital defines the valid keys for an item in the top-level
// `custom_host_vitals:` list. Definitions only (a name) -- per-host values are
// never set via GitOps.
type GitOpsCustomHostVital struct {
Name string `json:"name"`
}
type GitOpsSoftware struct {
@@ -506,7 +519,7 @@ func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig
result := &GitOps{}
result.FleetSecrets = make(map[string]string)
topKeys := []string{"name", "settings", "org_settings", "agent_options", "controls", "policies", "reports", "software", "labels"}
topKeys := []string{"name", "settings", "org_settings", "agent_options", "controls", "policies", "reports", "software", "labels", "custom_host_vitals"}
for k := range top {
if !slices.Contains(topKeys, k) {
multiError = multierror.Append(multiError, fmt.Errorf("unknown top-level field: %s", k))
@@ -569,9 +582,12 @@ func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig
for _, topKey := range topKeys {
// "name" is handled later with special logic based on the filename.
// "labels" and "software" are special cases where omitting may be a no-op (based on exception settings),
// rather than a directive to clear settings. settings keys were handled above.
if topKey == "name" || topKey == "labels" || topKey == "software" || topKey == "settings" || topKey == "org_settings" {
// "labels" and "software" are special cases where omitting may be a no-op (based on
// exception settings), rather than a directive to clear settings.
// "custom_host_vitals" has no exception setting -- omitting it always means clear-all -- but still needs its own
// presence tracking (parseCustomHostVitals below), so it's excluded from the generic
// null-default handling too. settings keys were handled above.
if topKey == "name" || topKey == "labels" || topKey == "software" || topKey == "custom_host_vitals" || topKey == "settings" || topKey == "org_settings" {
continue
}
// "controls" can be set on _either_ global or "no team" file, and we can't say which it is if both
@@ -599,6 +615,11 @@ func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig
multiError = parseLabels(top, result, baseDir, logFn, filePath, multiError)
}
}
// Get the custom host vitals. CustomHostVitalsPresent tracks whether the key was in the YAML.
if _, ok := top["custom_host_vitals"]; ok {
result.CustomHostVitalsPresent = true
multiError = parseCustomHostVitals(top, result, filePath, multiError)
}
// Get other top-level entities.
multiError = parseControls(top, result, logFn, filePath, multiError)
multiError = parseAgentOptions(top, result, baseDir, logFn, filePath, multiError)
@@ -1108,6 +1129,40 @@ func parseSecrets(result *GitOps, multiError *multierror.Error) *multierror.Erro
return multiError
}
// parseCustomHostVitals parses the top-level `custom_host_vitals:` key.
// Global-only: custom host vital definitions aren't team-scoped, so the key
// isn't valid on a team file. An empty (or explicitly null) list is a
// declarative clear-all, same as an absent secrets/yara_rules list.
func parseCustomHostVitals(top map[string]json.RawMessage, result *GitOps, filePath string, multiError *multierror.Error) *multierror.Error {
raw := top["custom_host_vitals"]
if !result.global() {
return multierror.Append(multiError, errors.New("'custom_host_vitals' cannot be set on a team file"))
}
result.CustomHostVitals = []fleet.CustomHostVital{}
if len(raw) == 0 || string(raw) == "null" {
return multiError
}
var vitals []GitOpsCustomHostVital
if err := json.Unmarshal(raw, &vitals); err != nil {
return multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"custom_host_vitals"}, err))
}
// Validate unknown keys in the custom_host_vitals section.
multiError = multierror.Append(multiError, validateRawKeys(raw, reflect.TypeFor[[]GitOpsCustomHostVital](), filePath, []string{"custom_host_vitals"})...)
for _, v := range vitals {
if err := fleet.ValidateCustomHostVitalName(v.Name); err != nil {
multiError = multierror.Append(multiError, fmt.Errorf("'custom_host_vitals': %w", err))
continue
}
result.CustomHostVitals = append(result.CustomHostVitals, fleet.CustomHostVital{Name: v.Name})
}
return multiError
}
func parseAgentOptions(top map[string]json.RawMessage, result *GitOps, baseDir string, logFn Logf, filePath string, multiError *multierror.Error) *multierror.Error {
agentOptionsRaw, ok := top["agent_options"]
if result.IsNoTeam() {
+82
View File
@@ -5145,6 +5145,88 @@ name: TestTeam
require.NoError(t, err)
assert.False(t, gitops.SoftwarePresent)
})
t.Run("custom host vitals present", func(t *testing.T) {
gitops, err := gitOpsFromString(t, `
org_settings:
server_settings:
server_url: https://example.com
org_info:
org_name: Test
custom_host_vitals:
- name: Asset tag
- name: Department
`)
require.NoError(t, err)
assert.True(t, gitops.CustomHostVitalsPresent)
assert.ElementsMatch(t, []fleet.CustomHostVital{{Name: "Asset tag"}, {Name: "Department"}}, gitops.CustomHostVitals)
})
t.Run("custom host vitals absent", func(t *testing.T) {
gitops, err := gitOpsFromString(t, `
org_settings:
server_settings:
server_url: https://example.com
org_info:
org_name: Test
`)
require.NoError(t, err)
assert.False(t, gitops.CustomHostVitalsPresent)
assert.Nil(t, gitops.CustomHostVitals, "absent custom_host_vitals should be nil")
})
t.Run("custom host vitals present but empty", func(t *testing.T) {
gitops, err := gitOpsFromString(t, `
org_settings:
server_settings:
server_url: https://example.com
org_info:
org_name: Test
custom_host_vitals:
`)
require.NoError(t, err)
assert.True(t, gitops.CustomHostVitalsPresent)
assert.Empty(t, gitops.CustomHostVitals)
})
}
func TestGitOpsCustomHostVitals(t *testing.T) {
t.Run("rejected on a team file", func(t *testing.T) {
path, basePath := createTempFile(t, "", `
name: TestTeam
custom_host_vitals:
- name: Asset tag
`)
_, err := GitOpsFromFile(path, basePath, nil, nopLogf)
require.ErrorContains(t, err, "'custom_host_vitals' cannot be set on a team file")
})
t.Run("rejects an invalid name", func(t *testing.T) {
_, err := gitOpsFromString(t, `
org_settings:
server_settings:
server_url: https://example.com
org_info:
org_name: Test
custom_host_vitals:
- name: " Asset tag"
`)
require.ErrorContains(t, err, "custom host vital name cannot have leading or trailing whitespace")
})
t.Run("rejects an unknown key", func(t *testing.T) {
_, err := gitOpsFromString(t, `
org_settings:
server_settings:
server_url: https://example.com
org_info:
org_name: Test
custom_host_vitals:
- name: Asset tag
id: 1
`)
require.Error(t, err)
})
}
func TestGitOpsFMACategoriesPresence(t *testing.T) {