mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
parent
fa0b595a10
commit
cbc7c29dff
4 changed files with 135 additions and 77 deletions
1
changes/21973-better-unmarshal-type-errors
Normal file
1
changes/21973-better-unmarshal-type-errors
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Improved fleetctl gitops type error mesages
|
||||
|
|
@ -48,7 +48,7 @@ func TestGitOpsTeamSofwareInstallers(t *testing.T) {
|
|||
{"testdata/gitops/team_software_installer_post_install_not_found.yml", "no such file or directory"},
|
||||
{"testdata/gitops/team_software_installer_no_url.yml", "at least one of hash_sha256 or url is required for each software package"},
|
||||
{"testdata/gitops/team_software_installer_invalid_self_service_value.yml",
|
||||
"\"packages.SoftwarePackageSpec.self_service\" must be a bool, found string"},
|
||||
"Couldn't edit \"../../fleetctl/testdata/gitops/team_software_installer_invalid_self_service_value.yml\" at \"software.packages.self_service\", expected type bool but got string"},
|
||||
{"testdata/gitops/team_software_installer_invalid_both_include_exclude.yml",
|
||||
`only one of "labels_exclude_any" or "labels_include_any" can be specified`},
|
||||
{"testdata/gitops/team_software_installer_valid_include.yml", ""},
|
||||
|
|
@ -303,7 +303,7 @@ func TestGitOpsNoTeamSoftwareInstallers(t *testing.T) {
|
|||
{"testdata/gitops/no_team_software_installer_post_install_not_found.yml", "no such file or directory"},
|
||||
{"testdata/gitops/no_team_software_installer_no_url.yml", "at least one of hash_sha256 or url is required for each software package"},
|
||||
{"testdata/gitops/no_team_software_installer_invalid_self_service_value.yml",
|
||||
"\"packages.SoftwarePackageSpec.self_service\" must be a bool, found string"},
|
||||
"Couldn't edit \"../../fleetctl/testdata/gitops/no-team.yml\" at \"software.packages.self_service\", expected type bool but got string"},
|
||||
{"testdata/gitops/no_team_software_installer_invalid_both_include_exclude.yml",
|
||||
`only one of "labels_exclude_any" or "labels_include_any" can be specified`},
|
||||
{"testdata/gitops/no_team_software_installer_valid_include.yml", ""},
|
||||
|
|
@ -419,7 +419,7 @@ func TestGitOpsTeamVPPApps(t *testing.T) {
|
|||
{"testdata/gitops/team_vpp_valid_empty.yml", "", time.Now().Add(-24 * time.Hour), map[string]uint{}},
|
||||
{"testdata/gitops/team_vpp_valid_app.yml", "VPP token expired", time.Now().Add(-24 * time.Hour), map[string]uint{}},
|
||||
{"testdata/gitops/team_vpp_invalid_app.yml", "app not available on vpp account", time.Now().Add(24 * time.Hour), map[string]uint{}},
|
||||
{"testdata/gitops/team_vpp_incorrect_type.yml", "\"app_store_apps.app_store_id\" must be a string, found number",
|
||||
{"testdata/gitops/team_vpp_incorrect_type.yml", "Couldn't edit \"../../fleetctl/testdata/gitops/team_vpp_incorrect_type.yml\" at \"software.app_store_apps.app_store_id\", expected type string but got number",
|
||||
time.Now().Add(24 * time.Hour), map[string]uint{}},
|
||||
{"testdata/gitops/team_vpp_empty_adamid.yml", "software app store id required", time.Now().Add(24 * time.Hour), map[string]uint{}},
|
||||
{"testdata/gitops/team_vpp_valid_app_labels_exclude_any.yml", "", time.Now().Add(24 * time.Hour),
|
||||
|
|
|
|||
|
|
@ -19,6 +19,72 @@ import (
|
|||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
type ParseTypeError struct {
|
||||
Filename string // The name of the file being parsed
|
||||
Keys []string // The complete path to the field
|
||||
Field string // The field we tried to assign to
|
||||
Type string // The type that we want to have
|
||||
Value string // The type of the value that we received
|
||||
err error // The original error
|
||||
}
|
||||
|
||||
func (e *ParseTypeError) Error() string {
|
||||
var keyPath []string
|
||||
keyPath = append(keyPath, e.Keys...)
|
||||
if e.Field != "" {
|
||||
var clearFields []string
|
||||
fields := strings.Split(e.Field, ".")
|
||||
fieldcheck:
|
||||
for _, field := range fields {
|
||||
for _, r := range field {
|
||||
// Any field name that contains an upper case letter is probably an embedded struct,
|
||||
// remove it from the field path to reduce end user confusion
|
||||
if unicode.IsUpper(r) {
|
||||
continue fieldcheck
|
||||
}
|
||||
}
|
||||
clearFields = append(clearFields, field)
|
||||
}
|
||||
keyPath = append(keyPath, strings.Join(clearFields, "."))
|
||||
}
|
||||
return fmt.Sprintf("Couldn't edit \"%s\" at \"%s\", expected type %s but got %s", e.Filename, strings.Join(keyPath, "."), e.Type, e.Value)
|
||||
}
|
||||
|
||||
func (e *ParseTypeError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func MaybeParseTypeError(filename string, keysPath []string, err error) error {
|
||||
unmarshallErr := &json.UnmarshalTypeError{}
|
||||
if errors.As(err, &unmarshallErr) {
|
||||
return &ParseTypeError{
|
||||
Filename: filename,
|
||||
Keys: keysPath,
|
||||
Field: unmarshallErr.Field,
|
||||
Type: unmarshallErr.Type.String(),
|
||||
Value: unmarshallErr.Value,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to unmarshal file \"%s\" key \"%s\": %w", filename, strings.Join(keysPath, "."), err)
|
||||
}
|
||||
|
||||
// YamlUnmarshal unmarshals YAML bytes into JSON and then into the output struct. We have to do this
|
||||
// because the yaml package stringifys the JSON parsing error before returning it, so we can't
|
||||
// extract field information to produce a helpful error for users.
|
||||
func YamlUnmarshal(yamlBytes []byte, out any) error {
|
||||
jsonBytes, err := yaml.YAMLToJSON(yamlBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal YAML to JSON: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(jsonBytes, out); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON bytes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BaseItem struct {
|
||||
Path *string `json:"path"`
|
||||
}
|
||||
|
|
@ -163,10 +229,10 @@ func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig
|
|||
if teamOk || teamSettingsOk {
|
||||
multiError = multierror.Append(multiError, errors.New("'org_settings' cannot be used with 'name', 'team_settings'"))
|
||||
} else {
|
||||
multiError = parseOrgSettings(orgSettingsRaw, result, baseDir, multiError)
|
||||
multiError = parseOrgSettings(orgSettingsRaw, result, baseDir, filePath, multiError)
|
||||
}
|
||||
case teamOk:
|
||||
multiError = parseName(teamRaw, result, multiError)
|
||||
multiError = parseName(teamRaw, result, filePath, multiError)
|
||||
if result.IsNoTeam() {
|
||||
if teamSettingsOk {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("cannot set 'team_settings' on 'No team' file: %q", filePath))
|
||||
|
|
@ -178,7 +244,7 @@ func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig
|
|||
if !teamSettingsOk {
|
||||
multiError = multierror.Append(multiError, errors.New("'team_settings' is required when 'name' is provided"))
|
||||
} else {
|
||||
multiError = parseTeamSettings(teamSettingsRaw, result, baseDir, multiError)
|
||||
multiError = parseTeamSettings(teamSettingsRaw, result, baseDir, filePath, multiError)
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
|
@ -195,26 +261,26 @@ func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig
|
|||
}
|
||||
result.Labels = make([]*fleet.LabelSpec, 0)
|
||||
} else {
|
||||
multiError = parseLabels(top, result, baseDir, multiError)
|
||||
multiError = parseLabels(top, result, baseDir, filePath, multiError)
|
||||
}
|
||||
// Get other top-level entities.
|
||||
multiError = parseControls(top, result, multiError, filePath)
|
||||
multiError = parseAgentOptions(top, result, baseDir, logFn, multiError)
|
||||
multiError = parseQueries(top, result, baseDir, logFn, multiError)
|
||||
multiError = parseAgentOptions(top, result, baseDir, logFn, filePath, multiError)
|
||||
multiError = parseQueries(top, result, baseDir, logFn, filePath, multiError)
|
||||
|
||||
if appConfig != nil && appConfig.License.IsPremium() {
|
||||
multiError = parseSoftware(top, result, baseDir, multiError)
|
||||
multiError = parseSoftware(top, result, baseDir, filePath, multiError)
|
||||
}
|
||||
|
||||
// Policies can reference software installers and scripts, thus we parse them after parseSoftware and parseControls.
|
||||
multiError = parsePolicies(top, result, baseDir, multiError)
|
||||
multiError = parsePolicies(top, result, baseDir, filePath, multiError)
|
||||
|
||||
return result, multiError.ErrorOrNil()
|
||||
}
|
||||
|
||||
func parseName(raw json.RawMessage, result *GitOps, multiError *multierror.Error) *multierror.Error {
|
||||
func parseName(raw json.RawMessage, result *GitOps, filePath string, multiError *multierror.Error) *multierror.Error {
|
||||
if err := json.Unmarshal(raw, &result.TeamName); err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to unmarshal name: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"name"}, err))
|
||||
}
|
||||
if result.TeamName == nil || *result.TeamName == "" {
|
||||
return multierror.Append(multiError, errors.New("team 'name' is required"))
|
||||
|
|
@ -243,10 +309,10 @@ func isNoTeam(teamName string) bool {
|
|||
|
||||
const noTeam = "No team"
|
||||
|
||||
func parseOrgSettings(raw json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
func parseOrgSettings(raw json.RawMessage, result *GitOps, baseDir string, filePath string, multiError *multierror.Error) *multierror.Error {
|
||||
var orgSettingsTop BaseItem
|
||||
if err := json.Unmarshal(raw, &orgSettingsTop); err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to unmarshal org_settings: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"org_settings"}, err))
|
||||
}
|
||||
noError := true
|
||||
if orgSettingsTop.Path != nil {
|
||||
|
|
@ -264,10 +330,10 @@ func parseOrgSettings(raw json.RawMessage, result *GitOps, baseDir string, multi
|
|||
)
|
||||
} else {
|
||||
var pathOrgSettings BaseItem
|
||||
if err := yaml.Unmarshal(fileBytes, &pathOrgSettings); err != nil {
|
||||
if err := YamlUnmarshal(fileBytes, &pathOrgSettings); err != nil {
|
||||
noError = false
|
||||
multiError = multierror.Append(
|
||||
multiError, fmt.Errorf("failed to unmarshal org settings file %s: %v", *orgSettingsTop.Path, err),
|
||||
multiError, MaybeParseTypeError(*orgSettingsTop.Path, []string{"org_settings"}, err),
|
||||
)
|
||||
} else {
|
||||
if pathOrgSettings.Path != nil {
|
||||
|
|
@ -284,9 +350,9 @@ func parseOrgSettings(raw json.RawMessage, result *GitOps, baseDir string, multi
|
|||
}
|
||||
}
|
||||
if noError {
|
||||
if err := yaml.Unmarshal(raw, &result.OrgSettings); err != nil {
|
||||
if err := YamlUnmarshal(raw, &result.OrgSettings); err != nil {
|
||||
// This error is currently unreachable because we know the file is valid YAML when we checked for nested path
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("failed to unmarshal org settings: %v", err))
|
||||
multiError = multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"org_settings"}, err))
|
||||
} else {
|
||||
multiError = parseSecrets(result, multiError)
|
||||
}
|
||||
|
|
@ -295,10 +361,10 @@ func parseOrgSettings(raw json.RawMessage, result *GitOps, baseDir string, multi
|
|||
return multiError
|
||||
}
|
||||
|
||||
func parseTeamSettings(raw json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
func parseTeamSettings(raw json.RawMessage, result *GitOps, baseDir string, filePath string, multiError *multierror.Error) *multierror.Error {
|
||||
var teamSettingsTop BaseItem
|
||||
if err := json.Unmarshal(raw, &teamSettingsTop); err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to unmarshal team_settings: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"team_settings"}, err))
|
||||
}
|
||||
noError := true
|
||||
if teamSettingsTop.Path != nil {
|
||||
|
|
@ -316,10 +382,10 @@ func parseTeamSettings(raw json.RawMessage, result *GitOps, baseDir string, mult
|
|||
)
|
||||
} else {
|
||||
var pathTeamSettings BaseItem
|
||||
if err := yaml.Unmarshal(fileBytes, &pathTeamSettings); err != nil {
|
||||
if err := YamlUnmarshal(fileBytes, &pathTeamSettings); err != nil {
|
||||
noError = false
|
||||
multiError = multierror.Append(
|
||||
multiError, fmt.Errorf("failed to unmarshal team settings file %s: %v", *teamSettingsTop.Path, err),
|
||||
multiError, MaybeParseTypeError(*teamSettingsTop.Path, []string{"team_settings"}, err),
|
||||
)
|
||||
} else {
|
||||
if pathTeamSettings.Path != nil {
|
||||
|
|
@ -336,9 +402,9 @@ func parseTeamSettings(raw json.RawMessage, result *GitOps, baseDir string, mult
|
|||
}
|
||||
}
|
||||
if noError {
|
||||
if err := yaml.Unmarshal(raw, &result.TeamSettings); err != nil {
|
||||
if err := YamlUnmarshal(raw, &result.TeamSettings); err != nil {
|
||||
// This error is currently unreachable because we know the file is valid YAML when we checked for nested path
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("failed to unmarshal team settings: %v", err))
|
||||
multiError = multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"team_settings"}, err))
|
||||
} else {
|
||||
multiError = parseSecrets(result, multiError)
|
||||
}
|
||||
|
|
@ -396,7 +462,7 @@ func parseSecrets(result *GitOps, multiError *multierror.Error) *multierror.Erro
|
|||
return multiError
|
||||
}
|
||||
|
||||
func parseAgentOptions(top map[string]json.RawMessage, result *GitOps, baseDir string, logFn Logf, multiError *multierror.Error) *multierror.Error {
|
||||
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() {
|
||||
if ok {
|
||||
|
|
@ -408,7 +474,7 @@ func parseAgentOptions(top map[string]json.RawMessage, result *GitOps, baseDir s
|
|||
}
|
||||
var agentOptionsTop BaseItem
|
||||
if err := json.Unmarshal(agentOptionsRaw, &agentOptionsTop); err != nil {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("failed to unmarshal agent_options: %v", err))
|
||||
multiError = multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"agent_options"}, err))
|
||||
} else {
|
||||
if agentOptionsTop.Path == nil {
|
||||
result.AgentOptions = &agentOptionsRaw
|
||||
|
|
@ -425,9 +491,9 @@ func parseAgentOptions(top map[string]json.RawMessage, result *GitOps, baseDir s
|
|||
)
|
||||
} else {
|
||||
var pathAgentOptions BaseItem
|
||||
if err := yaml.Unmarshal(fileBytes, &pathAgentOptions); err != nil {
|
||||
if err := YamlUnmarshal(fileBytes, &pathAgentOptions); err != nil {
|
||||
return multierror.Append(
|
||||
multiError, fmt.Errorf("failed to unmarshal agent options file %s: %v", *agentOptionsTop.Path, err),
|
||||
multiError, MaybeParseTypeError(*agentOptionsTop.Path, []string{"agent_options"}, err),
|
||||
)
|
||||
}
|
||||
if pathAgentOptions.Path != nil {
|
||||
|
|
@ -437,10 +503,10 @@ func parseAgentOptions(top map[string]json.RawMessage, result *GitOps, baseDir s
|
|||
)
|
||||
}
|
||||
var raw json.RawMessage
|
||||
if err := yaml.Unmarshal(fileBytes, &raw); err != nil {
|
||||
if err := YamlUnmarshal(fileBytes, &raw); err != nil {
|
||||
// This error is currently unreachable because we know the file is valid YAML when we checked for nested path
|
||||
return multierror.Append(
|
||||
multiError, fmt.Errorf("failed to unmarshal agent options file %s: %v", *agentOptionsTop.Path, err),
|
||||
multiError, MaybeParseTypeError(*agentOptionsTop.Path, []string{"agent_options"}, err),
|
||||
)
|
||||
}
|
||||
result.AgentOptions = &raw
|
||||
|
|
@ -459,7 +525,7 @@ func parseControls(top map[string]json.RawMessage, result *GitOps, multiError *m
|
|||
|
||||
var controlsTop GitOpsControls
|
||||
if err := json.Unmarshal(controlsRaw, &controlsTop); err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to unmarshal controls: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(yamlFilename, []string{"controls"}, err))
|
||||
}
|
||||
controlsTop.Defined = true
|
||||
controlsFilePath := yamlFilename
|
||||
|
|
@ -516,7 +582,7 @@ func parseControls(top map[string]json.RawMessage, result *GitOps, multiError *m
|
|||
}
|
||||
err = json.Unmarshal(data, &macOSSettings)
|
||||
if err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to process controls.macos_settings: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(controlsFilePath, []string{"controls", "macos_settings"}, err))
|
||||
}
|
||||
|
||||
for i := range macOSSettings.CustomSettings {
|
||||
|
|
@ -538,7 +604,7 @@ func parseControls(top map[string]json.RawMessage, result *GitOps, multiError *m
|
|||
}
|
||||
err = json.Unmarshal(data, &windowsSettings)
|
||||
if err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to process controls.windows_settings: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(controlsFilePath, []string{"controls", "windows_settings"}, err))
|
||||
}
|
||||
if windowsSettings.CustomSettings.Valid {
|
||||
for i := range windowsSettings.CustomSettings.Value {
|
||||
|
|
@ -575,8 +641,8 @@ func processControlsPathIfNeeded(controlsTop GitOpsControls, result *GitOps, con
|
|||
}
|
||||
|
||||
var pathControls GitOpsControls
|
||||
if err := yaml.Unmarshal(fileBytes, &pathControls); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal controls file %s: %v", *controlsTop.Path, err)
|
||||
if err := YamlUnmarshal(fileBytes, &pathControls); err != nil {
|
||||
return MaybeParseTypeError(*controlsTop.Path, []string{"controls"}, err)
|
||||
}
|
||||
if pathControls.Path != nil {
|
||||
return fmt.Errorf("nested paths are not supported: %s in %s", *pathControls.Path, *controlsTop.Path)
|
||||
|
|
@ -621,7 +687,7 @@ func resolveScriptPaths(input []BaseItem, baseDir string) ([]BaseItem, error) {
|
|||
return resolved, nil
|
||||
}
|
||||
|
||||
func parseLabels(top map[string]json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
func parseLabels(top map[string]json.RawMessage, result *GitOps, baseDir string, filePath string, multiError *multierror.Error) *multierror.Error {
|
||||
labelsRaw, ok := top["labels"]
|
||||
|
||||
// This shouldn't happen as we check for the property earlier,
|
||||
|
|
@ -632,7 +698,7 @@ func parseLabels(top map[string]json.RawMessage, result *GitOps, baseDir string,
|
|||
|
||||
var labels []Label
|
||||
if err := json.Unmarshal(labelsRaw, &labels); err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to unmarshal labels: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"labels"}, err))
|
||||
}
|
||||
for _, item := range labels {
|
||||
item := item
|
||||
|
|
@ -652,8 +718,8 @@ func parseLabels(top map[string]json.RawMessage, result *GitOps, baseDir string,
|
|||
)
|
||||
} else {
|
||||
var pathLabels []*Label
|
||||
if err := yaml.Unmarshal(fileBytes, &pathLabels); err != nil {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("failed to unmarshal labels file %s: %v", *item.Path, err))
|
||||
if err := YamlUnmarshal(fileBytes, &pathLabels); err != nil {
|
||||
multiError = multierror.Append(multiError, MaybeParseTypeError(*item.Path, []string{"labels"}, err))
|
||||
continue
|
||||
}
|
||||
for _, pq := range pathLabels {
|
||||
|
|
@ -716,14 +782,14 @@ func parseLabels(top map[string]json.RawMessage, result *GitOps, baseDir string,
|
|||
return multiError
|
||||
}
|
||||
|
||||
func parsePolicies(top map[string]json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
func parsePolicies(top map[string]json.RawMessage, result *GitOps, baseDir string, filePath string, multiError *multierror.Error) *multierror.Error {
|
||||
policiesRaw, ok := top["policies"]
|
||||
if !ok {
|
||||
return multierror.Append(multiError, errors.New("'policies' key is required"))
|
||||
}
|
||||
var policies []Policy
|
||||
if err := json.Unmarshal(policiesRaw, &policies); err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to unmarshal policies: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"policies"}, err))
|
||||
}
|
||||
for _, item := range policies {
|
||||
item := item
|
||||
|
|
@ -752,8 +818,8 @@ func parsePolicies(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
)
|
||||
} else {
|
||||
var pathPolicies []*Policy
|
||||
if err := yaml.Unmarshal(fileBytes, &pathPolicies); err != nil {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("failed to unmarshal policies file %s: %v", *item.Path, err))
|
||||
if err := YamlUnmarshal(fileBytes, &pathPolicies); err != nil {
|
||||
multiError = multierror.Append(multiError, MaybeParseTypeError(*item.Path, []string{"policies"}, err))
|
||||
continue
|
||||
}
|
||||
for _, pp := range pathPolicies {
|
||||
|
|
@ -869,8 +935,8 @@ func parsePolicyInstallSoftware(baseDir string, teamName *string, policy *Policy
|
|||
return fmt.Errorf("failed to read install_software.package_path file %q: %v", policy.InstallSoftware.PackagePath, err)
|
||||
}
|
||||
var policyInstallSoftwareSpec fleet.SoftwarePackageSpec
|
||||
if err := yaml.Unmarshal(fileBytes, &policyInstallSoftwareSpec); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal install_software.package_path file %s: %v", policy.InstallSoftware.PackagePath, err)
|
||||
if err := YamlUnmarshal(fileBytes, &policyInstallSoftwareSpec); err != nil {
|
||||
return MaybeParseTypeError(policy.InstallSoftware.PackagePath, []string{"policy", "install_software", "package_path"}, err)
|
||||
}
|
||||
installerOnTeamFound := false
|
||||
for _, pkg := range packages {
|
||||
|
|
@ -906,7 +972,7 @@ func parsePolicyInstallSoftware(baseDir string, teamName *string, policy *Policy
|
|||
return nil
|
||||
}
|
||||
|
||||
func parseQueries(top map[string]json.RawMessage, result *GitOps, baseDir string, logFn Logf, multiError *multierror.Error) *multierror.Error {
|
||||
func parseQueries(top map[string]json.RawMessage, result *GitOps, baseDir string, logFn Logf, filePath string, multiError *multierror.Error) *multierror.Error {
|
||||
queriesRaw, ok := top["queries"]
|
||||
if result.IsNoTeam() {
|
||||
if ok {
|
||||
|
|
@ -918,7 +984,7 @@ func parseQueries(top map[string]json.RawMessage, result *GitOps, baseDir string
|
|||
}
|
||||
var queries []Query
|
||||
if err := json.Unmarshal(queriesRaw, &queries); err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to unmarshal queries: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"queries"}, err))
|
||||
}
|
||||
for _, item := range queries {
|
||||
item := item
|
||||
|
|
@ -938,8 +1004,8 @@ func parseQueries(top map[string]json.RawMessage, result *GitOps, baseDir string
|
|||
)
|
||||
} else {
|
||||
var pathQueries []*Query
|
||||
if err := yaml.Unmarshal(fileBytes, &pathQueries); err != nil {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("failed to unmarshal queries file %s: %v", *item.Path, err))
|
||||
if err := YamlUnmarshal(fileBytes, &pathQueries); err != nil {
|
||||
multiError = multierror.Append(multiError, MaybeParseTypeError(*item.Path, []string{"queries"}, err))
|
||||
continue
|
||||
}
|
||||
for _, pq := range pathQueries {
|
||||
|
|
@ -988,7 +1054,7 @@ func parseQueries(top map[string]json.RawMessage, result *GitOps, baseDir string
|
|||
|
||||
var validSHA256Value = regexp.MustCompile(`\b[a-f0-9]{64}\b`)
|
||||
|
||||
func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir string, filePath string, multiError *multierror.Error) *multierror.Error {
|
||||
softwareRaw, ok := top["software"]
|
||||
if result.global() {
|
||||
if ok && string(softwareRaw) != "null" {
|
||||
|
|
@ -1000,16 +1066,7 @@ func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
var software Software
|
||||
if len(softwareRaw) > 0 {
|
||||
if err := json.Unmarshal(softwareRaw, &software); err != nil {
|
||||
var typeErr *json.UnmarshalTypeError
|
||||
if errors.As(err, &typeErr) {
|
||||
typeErrField := typeErr.Field
|
||||
if typeErrField == "" {
|
||||
// UnmarshalTypeError.Field is empty when trying to set an invalid type on the root node.
|
||||
typeErrField = "software"
|
||||
}
|
||||
return multierror.Append(multiError, fmt.Errorf("Couldn't edit software. %q must be a %s, found %s", typeErrField, typeErr.Type.String(), typeErr.Value))
|
||||
}
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to unmarshall softwarespec: %v", err))
|
||||
return multierror.Append(multiError, MaybeParseTypeError(filePath, []string{"software"}, err))
|
||||
}
|
||||
}
|
||||
for _, item := range software.AppStoreApps {
|
||||
|
|
@ -1055,8 +1112,8 @@ func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
multiError = multierror.Append(multiError, fmt.Errorf("failed to expand environmet in file %s: %v", *item.Path, err))
|
||||
continue
|
||||
}
|
||||
if err := yaml.Unmarshal(fileBytes, &softwarePackageSpec); err != nil {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("failed to unmarshal software package file %s: %v", *item.Path, err))
|
||||
if err := YamlUnmarshal(fileBytes, &softwarePackageSpec); err != nil {
|
||||
multiError = multierror.Append(multiError, MaybeParseTypeError(*item.Path, []string{"software", "packages"}, err))
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -530,7 +530,7 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"name"})
|
||||
config += "name: [2]\n"
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal name")
|
||||
assert.ErrorContains(t, err, "expected type string but got array")
|
||||
|
||||
// Missing team name
|
||||
config = getConfig([]string{"name"})
|
||||
|
|
@ -542,7 +542,7 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"team_settings"})
|
||||
config += "team_settings:\n path: [2]\n"
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal team_settings")
|
||||
assert.ErrorContains(t, err, "expected type string but got array")
|
||||
|
||||
// Invalid team_settings in a separate file
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "*team_settings.yml")
|
||||
|
|
@ -552,7 +552,7 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"team_settings"})
|
||||
config += fmt.Sprintf("%s:\n path: %s\n", "team_settings", tmpFile.Name())
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal team settings file")
|
||||
assert.ErrorContains(t, err, "expected type spec.BaseItem but got array")
|
||||
|
||||
// Invalid secrets 1
|
||||
config = getConfig([]string{"team_settings"})
|
||||
|
|
@ -606,7 +606,7 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"org_settings"})
|
||||
config += "org_settings:\n path: [2]\n"
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal org_settings")
|
||||
assert.ErrorContains(t, err, "expected type string but got array")
|
||||
|
||||
// Invalid org_settings in a separate file
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "*org_settings.yml")
|
||||
|
|
@ -616,7 +616,7 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"org_settings"})
|
||||
config += fmt.Sprintf("%s:\n path: %s\n", "org_settings", tmpFile.Name())
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal org settings file")
|
||||
assert.ErrorContains(t, err, "expected type spec.BaseItem but got array")
|
||||
|
||||
// Invalid secrets 1
|
||||
config = getConfig([]string{"org_settings"})
|
||||
|
|
@ -641,7 +641,7 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config := getConfig([]string{"agent_options"})
|
||||
config += "agent_options:\n path: [2]\n"
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal agent_options")
|
||||
assert.ErrorContains(t, err, "expected type string but got array")
|
||||
|
||||
// Invalid agent_options in a separate file
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "*agent_options.yml")
|
||||
|
|
@ -651,13 +651,13 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"agent_options"})
|
||||
config += fmt.Sprintf("%s:\n path: %s\n", "agent_options", tmpFile.Name())
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal agent options file")
|
||||
assert.ErrorContains(t, err, "expected type spec.BaseItem but got array")
|
||||
|
||||
// Invalid controls
|
||||
config = getConfig([]string{"controls"})
|
||||
config += "controls:\n path: [2]\n"
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal controls")
|
||||
assert.ErrorContains(t, err, "expected type string but got array")
|
||||
|
||||
// Invalid controls in a separate file
|
||||
tmpFile, err = os.CreateTemp(t.TempDir(), "*controls.yml")
|
||||
|
|
@ -667,13 +667,13 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"controls"})
|
||||
config += fmt.Sprintf("%s:\n path: %s\n", "controls", tmpFile.Name())
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal controls file")
|
||||
assert.ErrorContains(t, err, "expected type spec.GitOpsControls but got array")
|
||||
|
||||
// Invalid policies
|
||||
config = getConfig([]string{"policies"})
|
||||
config += "policies:\n path: [2]\n"
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal policies")
|
||||
assert.ErrorContains(t, err, "expected type []spec.Policy but got object")
|
||||
|
||||
// Invalid policies in a separate file
|
||||
tmpFile, err = os.CreateTemp(t.TempDir(), "*policies.yml")
|
||||
|
|
@ -683,7 +683,7 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"policies"})
|
||||
config += fmt.Sprintf("%s:\n - path: %s\n", "policies", tmpFile.Name())
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal policies file")
|
||||
assert.ErrorContains(t, err, "expected type spec.Policy but got number")
|
||||
|
||||
// Policy name missing
|
||||
config = getConfig([]string{"policies"})
|
||||
|
|
@ -701,7 +701,7 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"queries"})
|
||||
config += "queries:\n path: [2]\n"
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal queries")
|
||||
assert.ErrorContains(t, err, "expected type []spec.Query but got object")
|
||||
|
||||
// Invalid policies in a separate file
|
||||
tmpFile, err = os.CreateTemp(t.TempDir(), "*queries.yml")
|
||||
|
|
@ -711,7 +711,7 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
|||
config = getConfig([]string{"queries"})
|
||||
config += fmt.Sprintf("%s:\n - path: %s\n", "queries", tmpFile.Name())
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal queries file")
|
||||
assert.ErrorContains(t, err, "expected type spec.Query but got number")
|
||||
|
||||
// Query name missing
|
||||
config = getConfig([]string{"queries"})
|
||||
|
|
@ -1117,7 +1117,7 @@ software:
|
|||
Tier: fleet.TierPremium,
|
||||
}
|
||||
_, err = GitOpsFromFile(path, basePath, &appConfig, nopLogf)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal install_software.package_path file")
|
||||
assert.ErrorContains(t, err, "at \"policy.install_software.package_path\", expected type fleet.SoftwarePackageSpec but got string")
|
||||
}
|
||||
|
||||
func TestGitOpsWithStrayScriptEntryWithNoPath(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue