Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { LogManager } from "resource://normandy/lib/LogManager.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AddonRollbackAction:
BranchedAddonStudyAction:
MessagingExperimentAction:
PreferenceExperimentAction:
PreferenceRollbackAction:
PreferenceRolloutAction:
ShowHeartbeatAction:
});
const log = LogManager.getLogger("recipe-runner");
/**
* A class to manage the actions that recipes can use in Normandy.
*/
export class ActionsManager {
constructor() {
this.finalized = false;
this.localActions = {};
for (const [name, Constructor] of Object.entries(
ActionsManager.actionConstructors
)) {
this.localActions[name] = new Constructor();
}
}
static actionConstructors = {
"addon-rollback": lazy.AddonRollbackAction,
"addon-rollout": lazy.AddonRolloutAction,
"branched-addon-study": lazy.BranchedAddonStudyAction,
"console-log": lazy.ConsoleLogAction,
"messaging-experiment": lazy.MessagingExperimentAction,
"multi-preference-experiment": lazy.PreferenceExperimentAction,
"preference-rollback": lazy.PreferenceRollbackAction,
"preference-rollout": lazy.PreferenceRolloutAction,
"show-heartbeat": lazy.ShowHeartbeatAction,
};
static getCapabilities() {
// Prefix each action name with "action." to turn it into a capability name.
let capabilities = new Set();
for (const actionName of Object.keys(ActionsManager.actionConstructors)) {
capabilities.add(`action.${actionName}`);
}
return capabilities;
}
async processRecipe(recipe, suitability) {
let actionName = recipe.action;
if (actionName in this.localActions) {
log.info(`Executing recipe "${recipe.name}" (action=${recipe.action})`);
const action = this.localActions[actionName];
await action.processRecipe(recipe, suitability);
// If the recipe doesn't have matching capabilities, then a missing action
// is expected. In this case, don't send an error
} else if (
suitability !== lazy.BaseAction.suitability.CAPABILITIES_MISMATCH
) {
log.error(
`Could not execute recipe ${recipe.name}:`,
`Action ${recipe.action} is either missing or invalid.`
);
await lazy.Uptake.reportRecipe(recipe, lazy.Uptake.RECIPE_INVALID_ACTION);
}
}
async finalize(options) {
if (this.finalized) {
throw new Error("ActionsManager has already been finalized");
}
this.finalized = true;
// Finalize local actions
for (const action of Object.values(this.localActions)) {
action.finalize(options);
}
}
}