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:
Carlo 2026-04-02 17:22:14 -04:00 committed by GitHub
parent d4f48b6f9c
commit 48a4a327e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 186 additions and 39 deletions

View file

@ -0,0 +1 @@
- Fixed GitOps policy software resolution failing when URL lookup doesn't match, by falling back to hash-based lookup.

View file

@ -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
}
}

View file

@ -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)
})
}
}