merge main
This commit is contained in:
+59
-4
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user