Don't validate software/profile labels in dry run mode (#28201)

For #28154

This PR fixes a bug where GitOps dry runs would fail when software
installers or profiles referenced labels that were created in the same
run. The issue is that GitOps utilizes the real APIs for batch
software/profile creation for validation, sending a `dryRun` flag to
prevent those APIs from actually writing data. In dry run mode, no
labels are actually created, so validation checks for "don't use labels
that don't exist" will always fail when new labels are referenced.
Recent updates to GitOps have given it the ability to validate the
labels itself, removing the need to use the API for this check.

I added a new test for this in the mdm profiles tests. The test suite
for software installers is a little more challenging to update for this
case, and since it's not a happy path test I'm not prioritizing it, but
will try to add one time permitting.
This commit is contained in:
Scott Gress
2025-04-17 08:39:24 -05:00
committed by GitHub
parent 8a589c58e4
commit 47ac964768
4 changed files with 42 additions and 14 deletions
+1
View File
@@ -0,0 +1 @@
- Fixed issue where `fleetctl gitops --dry-run` would sometimes fail when creating and using labels in the same run
+6 -5
View File
@@ -1501,12 +1501,13 @@ func (svc *Service) BatchSetSoftwareInstallers(
fmt.Sprintf("Couldn't edit software. URL (%q) is invalid", payload.URL),
)
}
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, payload.LabelsIncludeAny, payload.LabelsExcludeAny)
if err != nil {
return "", err
if !dryRun {
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, payload.LabelsIncludeAny, payload.LabelsExcludeAny)
if err != nil {
return "", err
}
payload.ValidatedLabels = validatedLabels
}
payload.ValidatedLabels = validatedLabels
allScripts = append(allScripts, payload.InstallScript, payload.PostInstallScript, payload.UninstallScript)
}
+6 -3
View File
@@ -1635,9 +1635,12 @@ func (svc *Service) BatchSetMDMProfiles(
labels = append(labels, profiles[i].LabelsIncludeAny...)
labels = append(labels, profiles[i].LabelsExcludeAny...)
}
labelMap, err := svc.batchValidateProfileLabels(ctx, labels)
if err != nil {
return ctxerr.Wrap(ctx, err, "validating labels")
var labelMap map[string]fleet.ConfigurationProfileLabel
if !dryRun {
labelMap, err = svc.batchValidateProfileLabels(ctx, labels)
if err != nil {
return ctxerr.Wrap(ctx, err, "validating labels")
}
}
// We will not validate the profiles containing secret variables during dry run.
+29 -6
View File
@@ -1219,9 +1219,11 @@ func TestUploadWindowsMDMConfigProfileValidations(t *testing.T) {
{"duplicate profile name", 0, `<Replace>duplicate</Replace>`, true, "configuration profile with this name already exists"},
{"multiple Replace", 0, `<Replace>a</Replace><Replace>b</Replace>`, true, ""},
{"Replace and non-Replace", 0, `<Replace>a</Replace><Get>b</Get>`, true, "Windows configuration profiles can only have <Replace> or <Add> top level elements."},
{"BitLocker profile", 0,
{
"BitLocker profile", 0,
`<Replace><Item><Target><LocURI>./Device/Vendor/MSFT/BitLocker/AllowStandardUserEncryption</LocURI></Target></Item></Replace>`, true,
syncml.DiskEncryptionProfileRestrictionErrMsg},
syncml.DiskEncryptionProfileRestrictionErrMsg,
},
{"Windows updates profile", 0, `<Replace><Item><Target><LocURI> ./Device/Vendor/MSFT/Policy/Config/Update/ConfigureDeadlineNoAutoRebootForFeatureUpdates </LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include Windows updates settings."},
{"unsupported Fleet variable", 0, `<Replace>$FLEET_VAR_BOZO</Replace>`, true, "Fleet variable"},
@@ -1233,9 +1235,11 @@ func TestUploadWindowsMDMConfigProfileValidations(t *testing.T) {
{"team duplicate profile name", 1, `<Replace>duplicate</Replace>`, true, "configuration profile with this name already exists"},
{"team multiple Replace", 1, `<Replace>a</Replace><Replace>b</Replace>`, true, ""},
{"team Replace and non-Replace", 1, `<Replace>a</Replace><Get>b</Get>`, true, "Windows configuration profiles can only have <Replace> or <Add> top level elements."},
{"team BitLocker profile", 1,
{
"team BitLocker profile", 1,
`<Replace><Item><Target><LocURI>./Device/Vendor/MSFT/BitLocker/AllowStandardUserEncryption</LocURI></Target></Item></Replace>`, true,
syncml.DiskEncryptionProfileRestrictionErrMsg},
syncml.DiskEncryptionProfileRestrictionErrMsg,
},
{"team Windows updates profile", 1, `<Replace><Item><Target><LocURI> ./Device/Vendor/MSFT/Policy/Config/Update/ConfigureDeadlineNoAutoRebootForFeatureUpdates </LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include Windows updates settings."},
{"invalid team", 2, `<Replace></Replace>`, true, "not found"},
@@ -2117,8 +2121,10 @@ func TestBatchSetMDMProfilesLabels(t *testing.T) {
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
m := map[string]uint{}
for _, label := range labels {
labelID++
m[label] = labelID
if label != "baddy" {
labelID++
m[label] = labelID
}
}
return m, nil
}
@@ -2196,4 +2202,21 @@ func TestBatchSetMDMProfilesLabels(t *testing.T) {
assert.Equal(t, ProfileLabels{IncludeAll: true}, *profileLabels["DIncAll"])
assert.Equal(t, ProfileLabels{IncludeAny: true}, *profileLabels["DIncAny"])
assert.Equal(t, ProfileLabels{ExcludeAny: true}, *profileLabels["DExclAny"])
// Test that a bad label doesn't pass validation...
err = svc.BatchSetMDMProfiles(authCtx, ptr.Uint(1), nil, []fleet.MDMProfileBatchPayload{{
Name: "Baddy",
Contents: declarationForTest("Baddy"),
LabelsExcludeAny: []string{"baddy"},
}}, false, false, ptr.Bool(true), false)
require.Error(t, err)
require.ErrorContains(t, err, "some or all the labels provided don't exist")
// ...unless we're in dry run mode
err = svc.BatchSetMDMProfiles(authCtx, ptr.Uint(1), nil, []fleet.MDMProfileBatchPayload{{
Name: "Baddy",
Contents: declarationForTest("Baddy"),
LabelsExcludeAny: []string{"baddy"},
}}, true, false, ptr.Bool(true), false)
require.NoError(t, err)
}