Skip to content
This repository has been archived by the owner on Jul 31, 2021. It is now read-only.

Commit

Permalink
Merge pull request #119 from luisbritos/actions
Browse files Browse the repository at this point in the history
add support for Actions
  • Loading branch information
luisbritos authored Dec 22, 2020
2 parents 62fa2cf + f132faf commit 79a5f46
Show file tree
Hide file tree
Showing 9 changed files with 1,075 additions and 7 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "auth0-source-control-extension-tools",
"version": "4.1.12",
"version": "5.0.0",
"description": "Supporting tools for the Source Control extensions",
"main": "lib/index.js",
"scripts": {
Expand All @@ -20,6 +20,7 @@
"auth0-extension-tools": "^1.5.0",
"babel-preset-env": "^1.7.0",
"dot-prop": "^4.2.0",
"lodash": "^4.17.20",
"promise-pool-executor": "^1.1.1",
"rimraf": "^2.5.4",
"winston": "^2.2.0"
Expand Down
195 changes: 195 additions & 0 deletions src/auth0/handlers/actionBindings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import DefaultHandler from './default';
import log from '../../logger';

const triggers = [
'post-login',
'credentials-exchange'
];

function wait(n) { return new Promise(resolve => setTimeout(resolve, n)); }

async function createBindingWithRetryAndTimeout(client, params, data, retry) {
let binding = {};
try {
if (retry > 0) {
binding = await client.createActionBinding(params, data);
}
} catch (err) {
await wait(1000);
binding = await createBindingWithRetryAndTimeout(client, params, data, retry - 1);
}
return binding;
}

export default class ActionBindingHandler extends DefaultHandler {
constructor(options) {
super({
...options,
type: 'actionBindings'
});
}

async getType() {
if (this.existing) {
return this.existing;
}

// in case client version does not support actions
if (
!this.client.actionBindings
|| typeof this.client.actionBindings.getAll !== 'function'
) {
return [];
}
const bindingsResult = [];
try {
await Promise.all(
triggers.map(trigger => this.client.actionBindings
.getAll({ trigger_id: trigger, detached: true })
.then(b => b.bindings.forEach(binding => bindingsResult.push({ detached: true, ...binding }))))
);
// get all attached bindings
await Promise.all(
triggers.map(trigger => this.client.actionBindings
.getAll({ trigger_id: trigger, detached: false })
.then(b => b.bindings.forEach(binding => bindingsResult.push({ detached: false, ...binding }))))
);
this.existing = bindingsResult;
return this.existing;
} catch (err) {
if (err.statusCode === 404 || err.statusCode === 501) {
return [];
}
throw err;
}
}

async detachBinding(binding, detached) {
const params = { trigger_id: binding.trigger_id };
const existing = await this.getType();
let attachedList = [];
existing.forEach((existingBinding) => {
if (!existingBinding.detached) {
attachedList.push({ id: existingBinding.id });
}
});
if (!detached) {
attachedList.push({ id: binding.id });
} else {
attachedList = attachedList.filter(id => id === binding.id);
}
delete params.binding_id;
await this.client.actionBindings.updateList(params, {
bindings: attachedList
});
}

// Create Binding creates a atacched binding
async createActionBinding(data) {
const retries = 10;
const params = { trigger_id: data.trigger_id };
const actionBinding = { action_id: data.action_id, display_name: data.display_name };
const created = await createBindingWithRetryAndTimeout(this.client, params, actionBinding, retries);

if (!created) {
throw new Error(`Couldn't create binding after ${retries} retries`);
}
// connect binding
await this.detachBinding(created, false);

return created;
}

async createActionBindings(creates) {
await this.client.pool
.addEachTask({
data: creates || [],
generator: item => this.createActionBinding(item)
.then((data) => {
this.didCreate({ binding_id: data.id });
this.created += 1;
})
.catch((err) => {
throw new Error(
`Problem creating ${this.type} ${this.objString(item)}\n${err}`
);
})
})
.promise();
}

async deleteActionBinding(binding) {
// detach binding
await this.detachBinding(binding, true);
// delete binding
await this.client.actionBindings.delete({
trigger_id: binding.trigger_id,
binding_id: binding.id
});
}

async deleteActionBindings(dels) {
if (this.config('AUTH0_ALLOW_DELETE') === 'true' || this.config('AUTH0_ALLOW_DELETE') === true) {
await this.client.pool
.addEachTask({
data: dels || [],
generator: item => this.deleteActionBinding(item)
.then(() => {
this.didDelete({ binding_id: item.id });
this.deleted += 1;
})
.catch((err) => {
throw new Error(`Problem deleting ${this.type} ${this.objString(item)}\n${err}`);
})
})
.promise();
} else {
log.warn(`Detected the following actions bindings should be deleted. Doing so may be destructive.\nYou can enable deletes by setting 'AUTH0_ALLOW_DELETE' to true in the config
\n${dels.map(i => this.objString(i)).join('\n')}`);
}
}

async calcChanges(action, bindingsTriggers, existing) {
// Calculate the changes required between two sets of assets.
let del = [ ...existing ];
const create = [];

bindingsTriggers.forEach((binding) => {
const found = existing.find(
existingActionBinding => existingActionBinding.trigger_id === binding.trigger_id
);
if (found) {
del = del.filter(e => e !== found);
} else {
create.push({
display_name: action.name,
action_id: action.id,
trigger_id: binding.trigger_id
});
}
});
// Figure out what needs to be deleted and created
return { del, create };
}

async processChanges(changes) {
log.info(
`Start processChanges for actions bindings [delete:${changes.del.length}], [create:${changes.create.length}]`
);
const myChanges = [ { del: changes.del }, { create: changes.create } ];
await Promise.all(
myChanges.map(async (change) => {
switch (true) {
case change.del && change.del.length > 0:
await this.deleteActionBindings(change.del);
break;
case change.create && change.create.length > 0:
await this.createActionBindings(changes.create);
break;
default:
break;
}
})
);
}
}
154 changes: 154 additions & 0 deletions src/auth0/handlers/actionVersions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import DefaultHandler from './default';
import log from '../../logger';
import { isArrayEqual } from '../../utils';

export default class ActionVersionHandler extends DefaultHandler {
constructor(options) {
super({
...options,
type: 'actionVersions'
});
}

async getType(actionId) {
if (!actionId) {
return [];
}

if (this.existing) {
return this.existing;
}

// in case client version does not support actionVersions
if (!this.client.actionVersions || typeof this.client.actionVersions.getAll !== 'function') {
return [];
}

try {
this.existing = await this.client.actionVersions.getAll({ action_id: actionId });
return this.existing;
} catch (err) {
if (err.statusCode === 404 || err.statusCode === 501) {
return [];
}
throw err;
}
}

async getVersionById(actionId, currentVersion) {
// in case client version does not support actionVersions
if (!this.client.actionVersions || typeof this.client.actionVersions.get !== 'function') {
return null;
}
// in case action doesn't have a current version yet
if (!currentVersion) {
return null;
}

try {
return await this.client.actionVersions.get({ action_id: actionId, version_id: currentVersion.id });
} catch (err) {
if (err.statusCode === 404 || err.statusCode === 501) {
return null;
}
throw err;
}
}

async createActionVersion(version) {
const actionId = version.action_id;
const versionToCreate = {
code: version.code,
dependencies: version.dependencies,
secrets: version.secrets,
runtime: version.runtime
};
const newVersion = await this.client.actionVersions.create({ action_id: actionId }, versionToCreate);
// create draft version
await this.client.actionVersions.upsertDraft({ action_id: actionId, version_id: 'draft' }, versionToCreate);
return newVersion;
}

async createActionVersions(creates) {
await this.client.pool.addEachTask({
data: creates || [],
generator: item => this.createActionVersion(item).then((data) => {
this.didCreate({ version_id: data.id });
this.created += 1;
}).catch((err) => {
throw new Error(`Problem creating ${this.type} ${this.objString(item)}\n${err}`);
})
}).promise();
}

async deleteActionVersion(version) {
await this.client.actionVersions.delete({ action_id: version.action.id, version_id: version.id });
}

async deleteActionVersions(dels) {
if (this.config('AUTH0_ALLOW_DELETE') === 'true' || this.config('AUTH0_ALLOW_DELETE') === true) {
await this.client.pool.addEachTask({
data: dels || [],
generator: actionVersion => this.deleteActionVersion(actionVersion).then(() => {
this.didDelete({ version_id: actionVersion.id });
this.deleted += 1;
}).catch((err) => {
throw new Error(`Problem deleting ${this.type} ${this.objString(actionVersion)}\n${err}`);
})
}).promise();
} else {
log.warn(`Detected the following action versions should be deleted. Doing so may be destructive.\nYou can enable deletes by setting 'AUTH0_ALLOW_DELETE' to true in the config
\n${dels.map(i => this.objString(i)).join('\n')}`);
}
}

calcCurrentVersionChanges(actionId, currentVersionAssets, existing) {
const del = [];
const create = [];

// Figure out what needs to be deleted or created
if (!currentVersionAssets && !existing) {
return { del, create };
}

if (!currentVersionAssets && existing) {
del.push({ ...existing, action_id: actionId });
return { del, create };
}

if (currentVersionAssets && !existing) {
create.push({ ...currentVersionAssets, action_id: actionId });
return { del, create };
}

if (currentVersionAssets.code !== existing.code
|| currentVersionAssets.runtime !== existing.runtime
|| !isArrayEqual(currentVersionAssets.dependencies, existing.dependencies)
|| !isArrayEqual((currentVersionAssets.secrets || []).map(s => s.name), (existing.secrets || []).map(s => s.name))) {
create.push({ ...currentVersionAssets, action_id: actionId });
}

return {
del,
create
};
}

async processChanges(changes) {
log.info(`Start processChanges for action versions [delete:${changes.del.length}], [create:${changes.create.length}]`);

const myChanges = [ { del: changes.del }, { create: changes.create } ];
await Promise.all(myChanges.map(async (change) => {
switch (true) {
case change.del && change.del.length > 0:
await this.deleteActionVersions(change.del);
break;
case change.create && change.create.length > 0:
await this.createActionVersions(changes.create);
break;
default:
break;
}
}));
}
}
Loading

0 comments on commit 79a5f46

Please sign in to comment.