mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Fix GitOps policy-software resolution to fall back to hash when URL lookup fails (#42816)
Fixes #40841 ## Summary The root cause of the URL mismatch described in the issue is unknown. We couldn't reproduce it and couldn't find a deterministic code path that explains it. What we fix in this PR is a code defect that turns an unknown transient condition into a hard failure. When a policy has both a URL and a hash (which is always the case for `package_path` references), and the URL lookup fails for any reason, a continue statement prevented the hash-based fallback from ever running.
This commit is contained in:
parent
d4f48b6f9c
commit
48a4a327e6
3 changed files with 186 additions and 39 deletions
1
changes/40841-gitops-sw-upload-error
Normal file
1
changes/40841-gitops-sw-upload-error
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Fixed GitOps policy software resolution failing when URL lookup doesn't match, by falling back to hash-based lookup.
|
||||
|
|
@ -2812,6 +2812,40 @@ func (c *Client) doGitOpsLabels(
|
|||
return c.ApplyLabels(config.Labels, config.TeamID, namesToMove)
|
||||
}
|
||||
|
||||
// resolvePolicySoftwareTitleID attempts to resolve the software title ID for a
|
||||
// policy by trying each available identifier in order: URL, App Store ID, hash,
|
||||
// then FMA slug. Returns the resolved title ID and true if found, or 0 and
|
||||
// false if no identifier matched.
|
||||
func resolvePolicySoftwareTitleID(
|
||||
policy *spec.GitOpsPolicySpec,
|
||||
byURL, byAppStoreID, byHash, bySlug map[string]uint,
|
||||
) (titleID uint, resolved bool) {
|
||||
if policy.InstallSoftwareURL != "" {
|
||||
if id, ok := byURL[policy.InstallSoftwareURL]; ok {
|
||||
return id, true
|
||||
}
|
||||
}
|
||||
if policy.InstallSoftware.Other == nil {
|
||||
return 0, false
|
||||
}
|
||||
if policy.InstallSoftware.Other.AppStoreID != "" {
|
||||
if id, ok := byAppStoreID[policy.InstallSoftware.Other.AppStoreID]; ok {
|
||||
return id, true
|
||||
}
|
||||
}
|
||||
if policy.InstallSoftware.Other.HashSHA256 != "" {
|
||||
if id, ok := byHash[policy.InstallSoftware.Other.HashSHA256]; ok {
|
||||
return id, true
|
||||
}
|
||||
}
|
||||
if policy.InstallSoftware.Other.FleetMaintainedAppSlug != "" {
|
||||
if id, ok := bySlug[policy.InstallSoftware.Other.FleetMaintainedAppSlug]; ok {
|
||||
return id, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (c *Client) doGitOpsPolicies(config *spec.GitOps, teamSoftwareInstallers []fleet.SoftwarePackageResponse, teamVPPApps []fleet.VPPAppResponse, teamScripts []fleet.ScriptResponse, logFn func(format string, args ...interface{}), dryRun bool) error {
|
||||
// Collect policy names that have webhooks_and_tickets_enabled set.
|
||||
var policyNamesWithWebhooks []string
|
||||
|
|
@ -2900,49 +2934,34 @@ func (c *Client) doGitOpsPolicies(config *spec.GitOps, teamSoftwareInstallers []
|
|||
if config.Policies[i].InstallSoftware.Other == nil {
|
||||
continue
|
||||
}
|
||||
if config.Policies[i].InstallSoftwareURL != "" {
|
||||
softwareTitleID, ok := softwareTitleIDsByInstallerURL[config.Policies[i].InstallSoftwareURL]
|
||||
if !ok {
|
||||
// Should not happen because software packages are uploaded first.
|
||||
if !dryRun {
|
||||
logFn("[!] software URL without software title ID: %s\n", config.Policies[i].InstallSoftwareURL)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Try each identifier type in order of specificity. For package policies,
|
||||
// both URL and hash are set (from the referenced YAML file). If the primary
|
||||
// identifier (URL) fails, fall back to secondary identifiers rather than
|
||||
// skipping the policy entirely.
|
||||
softwareTitleID, resolved := resolvePolicySoftwareTitleID(
|
||||
config.Policies[i],
|
||||
softwareTitleIDsByInstallerURL,
|
||||
softwareTitleIDsByAppStoreAppID,
|
||||
softwareTitleIDsByHash,
|
||||
softwareTitleIDsBySlug,
|
||||
)
|
||||
if resolved {
|
||||
config.Policies[i].SoftwareTitleID = &softwareTitleID
|
||||
}
|
||||
if config.Policies[i].InstallSoftware.Other.AppStoreID != "" {
|
||||
softwareTitleID, ok := softwareTitleIDsByAppStoreAppID[config.Policies[i].InstallSoftware.Other.AppStoreID]
|
||||
if !ok {
|
||||
// Should not happen because app store apps are uploaded first.
|
||||
if !dryRun {
|
||||
logFn("[!] software app store app ID without software title ID: %s\n", config.Policies[i].InstallSoftware.Other.AppStoreID)
|
||||
// Log a warning if URL was set but didn't match (resolved via fallback).
|
||||
if !dryRun && config.Policies[i].InstallSoftwareURL != "" {
|
||||
if _, urlOK := softwareTitleIDsByInstallerURL[config.Policies[i].InstallSoftwareURL]; !urlOK {
|
||||
logFn("[!] policy %q: software URL lookup failed, resolved via fallback (url=%q, hash=%q)\n",
|
||||
config.Policies[i].Name, config.Policies[i].InstallSoftwareURL, config.Policies[i].InstallSoftware.Other.HashSHA256)
|
||||
}
|
||||
continue
|
||||
}
|
||||
config.Policies[i].SoftwareTitleID = &softwareTitleID
|
||||
}
|
||||
if config.Policies[i].InstallSoftware.Other.HashSHA256 != "" {
|
||||
softwareTitleID, ok := softwareTitleIDsByHash[config.Policies[i].InstallSoftware.Other.HashSHA256]
|
||||
if !ok {
|
||||
// Should not happen because software packages are uploaded first.
|
||||
if !dryRun {
|
||||
logFn("[!] software hash without software title ID: %s\n", config.Policies[i].InstallSoftware.Other.HashSHA256)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
if !dryRun {
|
||||
logFn("[!] policy %q: could not resolve software title ID (url=%q, hash=%q)\n",
|
||||
config.Policies[i].Name, config.Policies[i].InstallSoftwareURL,
|
||||
config.Policies[i].InstallSoftware.Other.HashSHA256)
|
||||
}
|
||||
config.Policies[i].SoftwareTitleID = &softwareTitleID
|
||||
}
|
||||
if config.Policies[i].InstallSoftware.Other.FleetMaintainedAppSlug != "" {
|
||||
softwareTitleID, ok := softwareTitleIDsBySlug[config.Policies[i].InstallSoftware.Other.FleetMaintainedAppSlug]
|
||||
if !ok {
|
||||
// Should not happen because FMAs are uploaded first.
|
||||
if !dryRun {
|
||||
logFn("[!] fleet-maintained app slug without software title ID: %s\n", config.Policies[i].InstallSoftware.Other.FleetMaintainedAppSlug)
|
||||
}
|
||||
continue
|
||||
}
|
||||
config.Policies[i].SoftwareTitleID = &softwareTitleID
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1047,3 +1047,130 @@ func TestGitOpsErrors(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePolicySoftwareTitleID(t *testing.T) {
|
||||
byURL := map[string]uint{
|
||||
"https://example.com/pkg.pkg": 100,
|
||||
}
|
||||
byAppStoreID := map[string]uint{
|
||||
"com.example.app": 200,
|
||||
}
|
||||
byHash := map[string]uint{
|
||||
"abc123hash": 100, // same title as the URL entry
|
||||
"different-hash": 999, // different title — used to test URL-over-hash precedence
|
||||
}
|
||||
bySlug := map[string]uint{
|
||||
"some-fma-slug": 300,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
policy *spec.GitOpsPolicySpec
|
||||
wantTitleID uint
|
||||
wantResolved bool
|
||||
}{
|
||||
{
|
||||
name: "URL lookup succeeds",
|
||||
policy: &spec.GitOpsPolicySpec{
|
||||
InstallSoftwareURL: "https://example.com/pkg.pkg",
|
||||
InstallSoftware: optjson.BoolOr[*spec.PolicyInstallSoftware]{
|
||||
IsOther: true,
|
||||
Other: &spec.PolicyInstallSoftware{
|
||||
HashSHA256: "abc123hash",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantTitleID: 100,
|
||||
wantResolved: true,
|
||||
},
|
||||
{
|
||||
name: "URL takes precedence over hash when both match different titles",
|
||||
policy: &spec.GitOpsPolicySpec{
|
||||
InstallSoftwareURL: "https://example.com/pkg.pkg",
|
||||
InstallSoftware: optjson.BoolOr[*spec.PolicyInstallSoftware]{
|
||||
IsOther: true,
|
||||
Other: &spec.PolicyInstallSoftware{
|
||||
HashSHA256: "different-hash",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantTitleID: 100, // URL's title (100), not hash's title (999)
|
||||
wantResolved: true,
|
||||
},
|
||||
{
|
||||
name: "URL lookup fails, hash fallback succeeds",
|
||||
policy: &spec.GitOpsPolicySpec{
|
||||
InstallSoftwareURL: "https://example.com/DIFFERENT-url.pkg",
|
||||
InstallSoftware: optjson.BoolOr[*spec.PolicyInstallSoftware]{
|
||||
IsOther: true,
|
||||
Other: &spec.PolicyInstallSoftware{
|
||||
HashSHA256: "abc123hash",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantTitleID: 100,
|
||||
wantResolved: true,
|
||||
},
|
||||
{
|
||||
name: "App Store ID lookup succeeds",
|
||||
policy: &spec.GitOpsPolicySpec{
|
||||
InstallSoftware: optjson.BoolOr[*spec.PolicyInstallSoftware]{
|
||||
IsOther: true,
|
||||
Other: &spec.PolicyInstallSoftware{
|
||||
AppStoreID: "com.example.app",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantTitleID: 200,
|
||||
wantResolved: true,
|
||||
},
|
||||
{
|
||||
name: "FMA slug lookup succeeds",
|
||||
policy: &spec.GitOpsPolicySpec{
|
||||
InstallSoftware: optjson.BoolOr[*spec.PolicyInstallSoftware]{
|
||||
IsOther: true,
|
||||
Other: &spec.PolicyInstallSoftware{
|
||||
FleetMaintainedAppSlug: "some-fma-slug",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantTitleID: 300,
|
||||
wantResolved: true,
|
||||
},
|
||||
{
|
||||
name: "all lookups fail",
|
||||
policy: &spec.GitOpsPolicySpec{
|
||||
InstallSoftwareURL: "https://example.com/nonexistent.pkg",
|
||||
InstallSoftware: optjson.BoolOr[*spec.PolicyInstallSoftware]{
|
||||
IsOther: true,
|
||||
Other: &spec.PolicyInstallSoftware{
|
||||
HashSHA256: "nonexistent-hash",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantTitleID: 0,
|
||||
wantResolved: false,
|
||||
},
|
||||
{
|
||||
name: "hash-only policy (no URL)",
|
||||
policy: &spec.GitOpsPolicySpec{
|
||||
InstallSoftware: optjson.BoolOr[*spec.PolicyInstallSoftware]{
|
||||
IsOther: true,
|
||||
Other: &spec.PolicyInstallSoftware{
|
||||
HashSHA256: "abc123hash",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantTitleID: 100,
|
||||
wantResolved: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
titleID, resolved := resolvePolicySoftwareTitleID(tt.policy, byURL, byAppStoreID, byHash, bySlug)
|
||||
require.Equal(t, tt.wantResolved, resolved)
|
||||
require.Equal(t, tt.wantTitleID, titleID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue