Skip to content
This repository was archived by the owner on Mar 1, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions src/make-gecko-action-hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
const _ = require('lodash');
const {getProjects, hgmoPath, scmLevel} = require('./util/projects');
const {getTaskclusterYml} = require('./util/tcyml');
const editRole = require('./util/edit-role');
const editHook = require('./util/edit-hook');
const {ACTION_HOOKS} = require('./util/action-hooks');
const chalk = require('chalk');

module.exports.setup = (program) => {
return program
.command('make-gecko-action-hooks')
.option('-n, --noop', 'Don\'t change roles, just show difference')
.description('create or update gecko in-tree action hooks');
};

module.exports.run = async function(options) {
let taskcluster = require('taskcluster-client');
let chalk = require('chalk');
let projects = await getProjects();

// We build action hooks' task definitions from the latest in-tree `.taskcluster.yml`.
const taskclusterYml = {
'mozilla-central': await getTaskclusterYml(projects['mozilla-central'].repo),
'comm-central': await getTaskclusterYml(projects['comm-central'].repo),
};

for (let action of ACTION_HOOKS) {
const hookGroupId = `project-${action.trustDomain}`;
const hookId = `in-tree-action-${action.level}-${action.actionPerm}`;
const projName = action.trustDomain === 'gecko' ? 'mozilla-central' : 'comm-central';
const {task, triggerSchema} = makeHookDetails(taskclusterYml[projName], action);
await editHook({
noop: options.noop,
hookGroupId,
hookId,
metadata: {
name: `Action task ${action.level}-${action.actionPerm}`,
description: [
'*DO NOT EDIT*',
'',
'This hook is configured automatically by',
'[taskcluster-admin](https://github.com/taskcluster/taskcluster-admin).',
].join('\n'),
owner: 'taskcluster-notifications@mozilla.com',
emailOnError: false, // true, TODO
},
schedule: [],
task,
triggerSchema,
});

// make the role with scopes assume:repo:<repo>:action:<actionPerm> for each repo at this level
const projectsAtLevel = Object.keys(projects)
.filter(p => projects[p].access === `scm_level_${action.level}`)
.map(p => projects[p]);
const scopes = projectsAtLevel.map(
project => `assume:repo:hg.mozilla.org/${hgmoPath(project)}:action:${action.actionPerm}`);
await editRole({
roleId: `hook-id:${hookGroupId}/${hookId}`,
description: [
'*DO NOT EDIT*',
'',
'This role is configured automatically by',
'[taskcluster-admin](https://github.com/taskcluster/taskcluster-admin).',
].join('\n'),
scopes,
noop: options.noop,
});
}
};

const makeHookDetails = (taskclusterYml, action) => {
const task = {
$let: {
tasks_for: 'action',
action: {
name: '${payload.decision.action.name}',
title: '${payload.decision.action.title}',
description: '${payload.decision.action.description}',
taskGroupId: '${payload.decision.action.taskGroupId}',
// Calculate the repo_scope. This is based on user input (the
// repository), but the hooks service checks that this is satisfied by
// the `hook-id:<hookGroupId>/<hookId>` role, which is set up above to
// only contain scopes for repositories at this level. Note that the
// actionPerm is *not* based on user input, but is fixed in the
// hookPayload template
repo_scope: 'assume:repo:${payload.decision.repository.url[8:]}:action:' + action.actionPerm,
// cb_name is user-specified for generic actions, but not for those with their own actionPerm
cb_name: action.actionPerm === 'generic' ?
'${payload.decision.action.cb_name}' :
action.actionPerm,
symbol: '${payload.decision.action.symbol}',
},
push: {$eval: 'payload.decision.push'},
repository: {$eval: 'payload.decision.repository'},
input: {$eval: 'payload.user.input'},
parameters: {$eval: 'payload.decision.parameters'},
task: {$eval: 'payload.user.task'},
taskId: {$eval: 'payload.user.taskId'},
taskGroupId: {$eval: 'payload.user.taskGroupId'},
// the hooks service provides the value of the new taskId
ownTaskId: {$eval: 'taskId'},
},
in: taskclusterYml.tasks[0],
};

const objSchema = (attrs, properties) => Object.assign({
type: 'object',
properties,
additionalProperties: false,
required: Object.keys(properties),
}, attrs);

const triggerSchema = objSchema({
description: [
'Information required to trigger this hook. This is provided by the `hookPayload`',
'template in the `actions.json` file generated in-tree.',
].join(' '),
}, {
decision: objSchema({
description: [
'Information provided by the decision task; this is usually baked into',
'`actions.json`, although any value could be supplied in a direct call to',
'`hooks.triggerHook`.',
].join(' '),
}, {
action: objSchema({description: 'Information about the action to perform'}, {
name: {type: 'string', description: 'hook name'},
title: {type: 'string', description: 'hook title'},
description: {type: 'string', description: 'hook description'},
taskGroupId: {type: 'string', description: 'taskGroupId of the decision task'},
cb_name: {type: 'string', description: 'name of the in-tree callback function'},
symbol: {type: 'string', description: 'treeherder symbol'},
}),
push: objSchema({description: 'Information about the push that created the decision task'}, {
owner: {type: 'string', description: 'user who made the original push'},
revision: {type: 'string', description: 'revision of the original push'},
pushlog_id: {type: 'string', description: 'Mercurial pushlog ID of the original push'},
}),
repository: objSchema({description: 'Information about the repository where the push occurred'}, {
url: {type: 'string', description: 'repository URL (without trailing slash)', pattern: '[^/]$'},
project: {type: 'string', description: 'repository project name (also known as "alias")'},
level: {type: 'string', description: 'repository SCM level'},
}),
parameters: {
type: 'object',
description: 'decision task parameters',
additionalProperties: true,
},
}),
user: objSchema({
description: 'Information provided by the user or user interface',
}, {
input: action.inputSchema ?
action.inputSchema :
{
anyOf: [
{type: 'object', description: 'user input for the task'},
{const: null, description: 'null when the action takes no input'},
],
},
taskId: {
anyOf: [
{type: 'string', description: 'taskId of the task on which this action was activated'},
{const: null, description: 'null when the action is activated for a taskGroup'},
],
},
taskGroupId: {type: 'string', description: 'taskGroupId on which this action was activated'},
}),
});

return {task, triggerSchema};
};
31 changes: 27 additions & 4 deletions src/make-gecko-branch-role.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ module.exports.run = async function(projectsOption, options) {
process.exit(1);
}

var roleId = `repo:hg.mozilla.org/${path}:*`;
var scopes = [
var roleId, scopes, description;

// repo:* role
roleId = `repo:hg.mozilla.org/${path}:*`;
scopes = [
`assume:${roleRoot}:branch:${domain}:level-${level}:${projectName}`,
];

Expand All @@ -66,7 +69,27 @@ module.exports.run = async function(projectsOption, options) {
scopes.push(scope);
}

var description = [
description = [
'*DO NOT EDIT*',
'',
`Scopes for all tasks (cron, action, push) related to https://hg.mozilla.org/${path}`,
'',
'This role is configured automatically by [taskcluster-admin](https://github.com/taskcluster/taskcluster-admin).',
].join('\n');

await editRole({
roleId,
description,
scopes,
noop: options.noop,
});

// repo:branch:default role
roleId = `repo:hg.mozilla.org/${path}:branch:default`;
scopes = [
`assume:${roleRoot}:push:${domain}:level-${level}:${projectName}`,
];
description = [
'*DO NOT EDIT*',
'',
`Scopes for tasks triggered from pushes to https://hg.mozilla.org/${path}`,
Expand All @@ -92,7 +115,7 @@ module.exports.run = async function(projectsOption, options) {
description = [
'*DO NOT EDIT*',
'',
`Scopes for nighlty cron tasks triggered from pushes to https://hg.mozilla.org/${path}`,
`Scopes for nightly cron tasks triggered from pushes to https://hg.mozilla.org/${path}`,
'',
'This role is configured automatically by ',
'[taskcluster-admin](https://github.com/taskcluster/taskcluster-admin).',
Expand Down
60 changes: 6 additions & 54 deletions src/make-gecko-cron-hook.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
const {getProjects, hgmoPath, scmLevel} = require('./util/projects');
const editRole = require('./util/edit-role');
const editHook = require('./util/edit-hook');
const chalk = require('chalk');
const taskcluster = require('taskcluster-client');
const {diffLines} = require('diff');

module.exports.setup = (program) => {
return program
Expand Down Expand Up @@ -76,9 +75,6 @@ var makeHook = async function(projectName, project, options) {
noop: options.noop,
});

// set up hook


var repo_env, checkout;
if (!project.gecko_repo) {
// If there isn't a gecko_repo associated with this project, then it is itself a gecko repo
Expand Down Expand Up @@ -189,54 +185,10 @@ var makeHook = async function(projectName, project, options) {
// set a property that is not a valid identifier
newHook.task.payload.cache[`level-${level}-checkouts`] = '/builds/worker/checkouts';

const hooks = new taskcluster.Hooks();

let hook;
try {
hook = await hooks.hook(hookGroupId, hookId);
delete hook['hookId'];
delete hook['hookGroupId'];
} catch (err) {
if (err.statusCode !== 404) {
throw err;
}
hook = {};
}

// compare and display the differences
const diffs = diffLines(
JSON.stringify(hook, null, 2),
JSON.stringify(newHook, null, 2),
{newlineIsToken: true});
let diffsFound = false;
diffs.forEach(diff => {
if (diff.added || diff.removed) {
diffsFound = true;
}
await editHook({
noop: options.noop,
hookGroupId,
hookId,
...newHook,
});

if (diffsFound) {
console.log(chalk.green.bold(`changes required for hook ${hookGroupId}/${hookId}:`));
diffs.forEach(diff => {
if (diff.added) {
diff.value.split(/\n/).forEach(l => console.log(chalk.green('+' + l)));
} else if (diff.removed) {
diff.value.split(/\n/).forEach(l => console.log(chalk.red('-' + l)));
} else {
diff.value.split(/\n/).forEach(l => console.log(' ' + l));
}
});
} else {
console.log(chalk.green.bold(`no changes required for hook ${hookGroupId}/${hookId}`));
}

if (!options.noop && diffsFound) {
if (hook.task) {
console.log(chalk.green.bold('updating hook'));
await hooks.updateHook(hookGroupId, hookId, newHook);
} else {
console.log(chalk.green.bold('creating hook'));
await hooks.createHook(hookGroupId, hookId, newHook);
}
}
};
14 changes: 13 additions & 1 deletion src/make-gecko-parameterized-roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module.exports.run = async function(options) {
await editRole({
roleId: `${roleRoot}:branch:${domain}:level-${level}:*`,
description: description(
`Scopes for ${domain} projects at level ${level}; the '*' matches the project name.`
`Scopes for tasks associated with all ${domain} projects at level ${level}; the '*' matches the project name.`
),
scopes: [
`assume:moz-tree:level:${level}:${domain}`,
Expand All @@ -54,6 +54,18 @@ module.exports.run = async function(options) {
noop: options.noop,
});

await editRole({
roleId: `${roleRoot}:push:${domain}:level-${level}:*`,
description: description(
`Scopes for tasks associated with pushes to ${domain} projects at level ${level};
the '*' matches the project name.`
),
scopes: [
`in-tree:hook-action:project-${domain}/in-tree-action-${level}-*`,
],
noop: options.noop,
});

let makeFeature = async (feature, scopes) => {
await editRole({
roleId: `${roleRoot}:feature:${feature}:${domain}:level-${level}:*`,
Expand Down
43 changes: 0 additions & 43 deletions src/make-scm-group-role.js

This file was deleted.

Loading