Fleet UI: Update error messages for config variables (#45467)

This commit is contained in:
RachelElysia
2026-05-15 07:15:29 -04:00
committed by GitHub
parent c19df6d2c7
commit 9959bcd192
5 changed files with 238 additions and 0 deletions
@@ -0,0 +1 @@
- Added managed app configuration for iOS and iPadOS apps (VPP and in-house), configurable via UI, REST API, and GitOps, with `$FLEET_VAR_*` substitution.
@@ -97,6 +97,112 @@ describe("getErrorMessage", () => {
"Couldn't update configuration. Please try again."
);
});
it("returns 'doesn't exist' for an unknown $FLEET_VAR_ variable", () => {
const err = {
response: {
data: {
errors: [
{
name: "configuration",
reason: "unsupported variable $FLEET_VAR_BLA_BLA",
},
],
},
},
};
expect(getErrorMessage(err, true)).toBe(
`Couldn't edit. Variable "$FLEET_VAR_BLA_BLA" doesn't exist.`
);
});
it.each([
["NDES_SCEP_CHALLENGE"],
["NDES_SCEP_PROXY_URL"],
["DIGICERT_DATA_myCA"],
["DIGICERT_PASSWORD_myCA"],
["SCEP_WINDOWS_CERTIFICATE_ID"],
["SMALLSTEP_SCEP_CHALLENGE_myCA"],
["SMALLSTEP_SCEP_PROXY_URL_myCA"],
["CUSTOM_SCEP_CHALLENGE_myCA"],
["CUSTOM_SCEP_PROXY_URL_myCA"],
["SCEP_RENEWAL_ID"],
])(
"returns 'isn't supported in managed configuration' for %s",
(varSuffix) => {
const err = {
response: {
data: {
errors: [
{
name: "configuration",
reason: `unsupported variable $FLEET_VAR_${varSuffix}`,
},
],
},
},
};
expect(getErrorMessage(err, true)).toBe(
`Couldn't edit. Variable "$FLEET_VAR_${varSuffix}" isn't supported in managed configuration. It can only be used in configuration profiles.`
);
}
);
it("returns 'doesn't exist' for a typo like $FLEET_VAR_NDES_SCEP_FOO (not a real profile variable)", () => {
const err = {
response: {
data: {
errors: [
{
name: "configuration",
reason: "unsupported variable $FLEET_VAR_NDES_SCEP_FOO",
},
],
},
},
};
expect(getErrorMessage(err, true)).toBe(
`Couldn't edit. Variable "$FLEET_VAR_NDES_SCEP_FOO" doesn't exist.`
);
});
it("returns 'doesn't exist' for a missing $FLEET_SECRET_ variable", () => {
const err = {
response: {
data: {
errors: [
{
name: "configuration",
reason:
'Couldn\'t add. Secret variable "$FLEET_SECRET_BLA_BLA" missing from database',
},
],
},
},
};
expect(getErrorMessage(err, true)).toBe(
`Couldn't edit. Variable "$FLEET_SECRET_BLA_BLA" doesn't exist.`
);
});
it("returns 'doesn't exist' listing all variables for multiple missing $FLEET_SECRET_ variables", () => {
const err = {
response: {
data: {
errors: [
{
name: "configuration",
reason:
'Couldn\'t add. Secret variables "$FLEET_SECRET_A", "$FLEET_SECRET_B" missing from database',
},
],
},
},
};
expect(getErrorMessage(err, true)).toBe(
`Couldn't edit. Variables "$FLEET_SECRET_A", "$FLEET_SECRET_B" don't exist.`
);
});
});
describe("validateJson", () => {
@@ -5,6 +5,54 @@ import { getErrorReason } from "interfaces/errors";
const DEFAULT_ERROR_MESSAGE =
"Couldn't update configuration. Please try again.";
/** Variables that are valid in configuration profiles but NOT in managed configuration. */
const PROFILE_ONLY_VARIABLE_PATTERNS = [
/^NDES_SCEP_(CHALLENGE|PROXY_URL)$/,
/^CUSTOM_SCEP_(CHALLENGE|PROXY_URL)_/,
/^SCEP_RENEWAL_ID$/,
/^DIGICERT_(DATA|PASSWORD)_/,
/^SCEP_WINDOWS_CERTIFICATE_ID$/,
/^SMALLSTEP_SCEP_(CHALLENGE|PROXY_URL)_/,
];
const isProfileOnlyVariable = (varNameWithoutPrefix: string): boolean => {
return PROFILE_ONLY_VARIABLE_PATTERNS.some((pattern) =>
pattern.test(varNameWithoutPrefix)
);
};
const generateUnsupportedVariableErrMsg = (errMsg: string) => {
const match = errMsg.match(/\$FLEET_VAR_(\w+)/);
if (!match) {
return DEFAULT_ERROR_MESSAGE;
}
const fullVarName = match[0];
const varNameWithoutPrefix = match[1];
if (isProfileOnlyVariable(varNameWithoutPrefix)) {
return `Couldn't edit. Variable "${fullVarName}" isn't supported in managed configuration. It can only be used in configuration profiles.`;
}
return `Couldn't edit. Variable "${fullVarName}" doesn't exist.`;
};
const generateMissingSecretErrMsg = (errMsg: string) => {
const regex = /"\$FLEET_SECRET_\w+"/g;
const varNames: string[] = [];
let m = regex.exec(errMsg);
while (m) {
varNames.push(m[0].replace(/"/g, ""));
m = regex.exec(errMsg);
}
if (varNames.length === 0) {
return DEFAULT_ERROR_MESSAGE;
}
const plural = varNames.length > 1 ? "s" : "";
const verb = varNames.length > 1 ? "don't" : "doesn't";
const quoted = varNames.map((v) => `"${v}"`).join(", ");
return `Couldn't edit. Variable${plural} ${quoted} ${verb} exist.`;
};
export const getErrorMessage = (err: unknown, isApplePlatform: boolean) => {
const reason = getErrorReason(err);
@@ -23,6 +71,26 @@ export const getErrorMessage = (err: unknown, isApplePlatform: boolean) => {
);
}
// Fleet variable ($FLEET_VAR_) unsupported in managed configuration.
// Note: the backend validates $FLEET_VAR_ variables one at a time and
// returns on the first unsupported one it finds, so only one variable is
// surfaced per request even if the configuration contains multiple invalid
// variables. $FLEET_SECRET_ errors can contain multiple variables.
if (
reason.includes("unsupported variable") &&
reason.includes("$FLEET_VAR_")
) {
return generateUnsupportedVariableErrMsg(reason);
}
// Secret variable missing from database
if (
reason.includes("missing from database") &&
reason.includes("$FLEET_SECRET_")
) {
return generateMissingSecretErrMsg(reason);
}
return reason || DEFAULT_ERROR_MESSAGE;
};
@@ -0,0 +1,59 @@
import { getInstallErrorMessage } from "./helpers";
const makeErr = (reason: string) => ({
response: {
data: {
errors: [{ name: "base", reason }],
},
},
});
describe("getInstallErrorMessage", () => {
it("returns fleetd-specific message", () => {
const result = getInstallErrorMessage(
makeErr("host has fleetd installed from another source")
);
expect(result).toContain("fleetd installed");
});
it("returns macOS-only install message", () => {
const result = getInstallErrorMessage(
makeErr("software can be installed only on darwin")
);
expect(result).toBe(
"Couldn't install. Software can be installed only on macOS."
);
});
it("returns MDM turned off message as-is", () => {
const result = getInstallErrorMessage(makeErr("MDM is turned off."));
expect(result).toBe("MDM is turned off");
});
it("returns no available licenses message as-is", () => {
const result = getInstallErrorMessage(makeErr("No available licenses."));
expect(result).toBe("No available licenses");
});
it("returns unresolvable Fleet variable message", () => {
const result = getInstallErrorMessage(
makeErr(
"apple_mdm: unresolvable Fleet variable in managed app configuration"
)
);
expect(result).toBe(
"Couldn't install. Couldn't resolve a Fleet variable in the managed app configuration for this host."
);
});
it("returns default message for unknown errors", () => {
const result = getInstallErrorMessage(makeErr("something unexpected"));
expect(result).toBe("Couldn't install. Please try again.");
});
it("returns default message when no reason can be extracted", () => {
expect(getInstallErrorMessage({})).toBe(
"Couldn't install. Please try again."
);
});
});
@@ -62,6 +62,10 @@ export const getInstallErrorMessage = (e: unknown) => {
return createOnlyInstallableOnMacOSMessage(reason);
} else if (reason.includes("VPP token expired")) {
return createVPPTokenExpiredMessage();
} else if (
reason.includes("unresolvable Fleet variable in managed app configuration")
) {
return `${INSTALL_SOFTWARE_ERROR_PREFIX} Couldn't resolve a Fleet variable in the managed app configuration for this host.`;
} else if (showAPIMessage(reason)) {
return reason;
}