@heroku/skynet 1.13.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/allowlist/add.d.ts +11 -0
- package/dist/commands/allowlist/add.js +34 -0
- package/dist/commands/allowlist/remove.d.ts +10 -0
- package/dist/commands/allowlist/remove.js +27 -0
- package/dist/commands/allowlists.d.ts +7 -0
- package/dist/commands/allowlists.js +24 -0
- package/dist/commands/categories/add.d.ts +10 -0
- package/dist/commands/categories/add.js +33 -0
- package/dist/commands/categories/get.d.ts +7 -0
- package/dist/commands/categories/get.js +19 -0
- package/dist/commands/categories/remove.d.ts +9 -0
- package/dist/commands/categories/remove.js +26 -0
- package/dist/commands/deprovision.d.ts +14 -0
- package/dist/commands/deprovision.js +80 -0
- package/dist/commands/suspend/app-owner.d.ts +15 -0
- package/dist/commands/suspend/app-owner.js +87 -0
- package/dist/commands/suspend/apps.d.ts +13 -0
- package/dist/commands/suspend/apps.js +54 -0
- package/dist/commands/suspend/user.d.ts +17 -0
- package/dist/commands/suspend/user.js +110 -0
- package/dist/commands/suspensions.d.ts +9 -0
- package/dist/commands/suspensions.js +25 -0
- package/dist/commands/unsuspend/apps.d.ts +11 -0
- package/dist/commands/unsuspend/apps.js +48 -0
- package/dist/commands/unsuspend/user.d.ts +11 -0
- package/dist/commands/unsuspend/user.js +49 -0
- package/dist/commands/userpass/add.d.ts +10 -0
- package/dist/commands/userpass/add.js +33 -0
- package/dist/commands/userpass/remove.d.ts +10 -0
- package/dist/commands/userpass/remove.js +33 -0
- package/dist/lib/heroku.d.ts +13 -0
- package/dist/lib/heroku.js +32 -0
- package/dist/lib/skynet.d.ts +32 -0
- package/dist/lib/skynet.js +215 -0
- package/dist/lib/sudo.d.ts +1 -0
- package/dist/lib/sudo.js +7 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +57 -0
- package/oclif.manifest.json +730 -0
- package/package.json +61 -23
- package/commands/allowlist/add.js +0 -38
- package/commands/allowlist/remove.js +0 -29
- package/commands/allowlists.js +0 -30
- package/commands/categories/add.js +0 -40
- package/commands/categories/get.js +0 -27
- package/commands/categories/remove.js +0 -33
- package/commands/deprovision.js +0 -42
- package/commands/suspend/app-owner.js +0 -58
- package/commands/suspend/apps.js +0 -34
- package/commands/suspend/user.js +0 -80
- package/commands/suspensions.js +0 -24
- package/commands/unsuspend/apps.js +0 -33
- package/commands/unsuspend/user.js +0 -33
- package/commands/userpass/add.js +0 -21
- package/commands/userpass/remove.js +0 -21
- package/index.js +0 -23
- package/lib/command.js +0 -12
- package/lib/heroku.js +0 -30
- package/lib/notifyOption.js +0 -24
- package/lib/skynet.js +0 -218
- package/lib/sudo.js +0 -5
- package/lib/utils.js +0 -35
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class AddAllowlist extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
value: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
notes: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
static hiddenAliases: string[];
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import SkynetAPI from '../../lib/skynet.js';
|
|
2
|
+
import { Command } from '@heroku-cli/command';
|
|
3
|
+
import { color } from '@heroku-cli/color';
|
|
4
|
+
import { Flags, ux } from '@oclif/core';
|
|
5
|
+
export default class AddAllowlist extends Command {
|
|
6
|
+
static description = 'adds an app or user to the allowlist';
|
|
7
|
+
static examples = [
|
|
8
|
+
'$ heroku skynet:allowlist:add -v foo@bar.com -n Additional info'
|
|
9
|
+
];
|
|
10
|
+
static flags = {
|
|
11
|
+
value: Flags.string({
|
|
12
|
+
name: 'value',
|
|
13
|
+
char: 'v',
|
|
14
|
+
description: 'app name or user email to allowlist',
|
|
15
|
+
hasValue: true,
|
|
16
|
+
required: true
|
|
17
|
+
}),
|
|
18
|
+
notes: Flags.string({
|
|
19
|
+
name: 'notes',
|
|
20
|
+
char: 'n',
|
|
21
|
+
description: 'additional information for the allowlist entry',
|
|
22
|
+
hasValue: true,
|
|
23
|
+
required: true
|
|
24
|
+
})
|
|
25
|
+
};
|
|
26
|
+
static hiddenAliases = ['allowlist:append'];
|
|
27
|
+
async run() {
|
|
28
|
+
const { flags } = await this.parse(AddAllowlist);
|
|
29
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
30
|
+
ux.action.start(`adding ${color.cyan(flags.value)} to allowlist with ${color.cyan(flags.notes)}`);
|
|
31
|
+
await skynet.addAllowlist(flags.value, flags.notes);
|
|
32
|
+
ux.action.stop();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class RemoveAllowlist extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
value: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
static hiddenAliases: string[];
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import SkynetAPI from '../../lib/skynet.js';
|
|
2
|
+
import { Command } from '@heroku-cli/command';
|
|
3
|
+
import { color } from '@heroku-cli/color';
|
|
4
|
+
import { Flags, ux } from '@oclif/core';
|
|
5
|
+
export default class RemoveAllowlist extends Command {
|
|
6
|
+
static description = 'Remove user or app from the allowlist';
|
|
7
|
+
static examples = [
|
|
8
|
+
'$ heroku skynet:allowlist:remove -v foo@bar.com'
|
|
9
|
+
];
|
|
10
|
+
static flags = {
|
|
11
|
+
value: Flags.string({
|
|
12
|
+
name: 'value',
|
|
13
|
+
char: 'v',
|
|
14
|
+
description: 'app name or user email to remove from allowlist',
|
|
15
|
+
hasValue: true,
|
|
16
|
+
required: true
|
|
17
|
+
})
|
|
18
|
+
};
|
|
19
|
+
static hiddenAliases = ['allowlist:remove'];
|
|
20
|
+
async run() {
|
|
21
|
+
const { flags } = await this.parse(RemoveAllowlist);
|
|
22
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
23
|
+
ux.action.start(`removing ${color.cyan(flags.value)} from allowlist`);
|
|
24
|
+
await skynet.removeAllowlist(flags.value);
|
|
25
|
+
ux.action.stop();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import SkynetAPI from '../lib/skynet.js';
|
|
2
|
+
import { Command } from '@heroku-cli/command';
|
|
3
|
+
import { ux } from '@oclif/core';
|
|
4
|
+
export default class Allowlists extends Command {
|
|
5
|
+
static description = 'allowlists used by skynet';
|
|
6
|
+
static examples = [
|
|
7
|
+
'$ heroku skynet:allowlists'
|
|
8
|
+
];
|
|
9
|
+
static flags = {};
|
|
10
|
+
async run() {
|
|
11
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
12
|
+
let response = await skynet.allowlists();
|
|
13
|
+
response = JSON.parse(response);
|
|
14
|
+
if (Object.keys(response).length > 0) {
|
|
15
|
+
ux.table(response, {
|
|
16
|
+
Value: {},
|
|
17
|
+
Notes: {},
|
|
18
|
+
CreatedAt: {},
|
|
19
|
+
UpdatedAt: {},
|
|
20
|
+
DeletedAt: {}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class AddCategory extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
name: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
description: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import SkynetAPI from '../../lib/skynet.js';
|
|
2
|
+
import { Command } from '@heroku-cli/command';
|
|
3
|
+
import { color } from '@heroku-cli/color';
|
|
4
|
+
import { Flags, ux } from '@oclif/core';
|
|
5
|
+
export default class AddCategory extends Command {
|
|
6
|
+
static description = '(requires sudo) Adds a category for use when suspending or deprovisioning';
|
|
7
|
+
static examples = [
|
|
8
|
+
'$ heroku skynet:categories:add -n Cryptominer -d "Abusers running cryptomining on the platform"'
|
|
9
|
+
];
|
|
10
|
+
static flags = {
|
|
11
|
+
name: Flags.string({
|
|
12
|
+
name: 'name',
|
|
13
|
+
char: 'n',
|
|
14
|
+
description: 'The name of the category',
|
|
15
|
+
hasValue: true,
|
|
16
|
+
required: true
|
|
17
|
+
}),
|
|
18
|
+
description: Flags.string({
|
|
19
|
+
name: 'description',
|
|
20
|
+
char: 'd',
|
|
21
|
+
description: 'The description of the category',
|
|
22
|
+
hasValue: true,
|
|
23
|
+
required: true
|
|
24
|
+
})
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { flags } = await this.parse(AddCategory);
|
|
28
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
29
|
+
ux.action.start(`adding ${color.cyan(flags.name)} to categories with description ${color.cyan(flags.description)}`);
|
|
30
|
+
await skynet.addCategory(flags.name, flags.description);
|
|
31
|
+
ux.action.stop();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import SkynetAPI from '../../lib/skynet.js';
|
|
2
|
+
import { Command } from '@heroku-cli/command';
|
|
3
|
+
import { ux } from '@oclif/core';
|
|
4
|
+
export default class GetCategories extends Command {
|
|
5
|
+
static description = '(requires sudo) categories to use for suspension and deprovisions';
|
|
6
|
+
static examples = [
|
|
7
|
+
'$ heroku skynet:categories'
|
|
8
|
+
];
|
|
9
|
+
static flags = {};
|
|
10
|
+
async run() {
|
|
11
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
12
|
+
let response = await skynet.categories();
|
|
13
|
+
response = JSON.parse(response);
|
|
14
|
+
ux.table(response, {
|
|
15
|
+
Category: {},
|
|
16
|
+
Description: {}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class RemoveCategory extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
name: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import SkynetAPI from '../../lib/skynet.js';
|
|
2
|
+
import { Command } from '@heroku-cli/command';
|
|
3
|
+
import { color } from '@heroku-cli/color';
|
|
4
|
+
import { Flags, ux } from '@oclif/core';
|
|
5
|
+
export default class RemoveCategory extends Command {
|
|
6
|
+
static description = '(requires sudo) Removes a category from Skynet';
|
|
7
|
+
static examples = [
|
|
8
|
+
'$ heroku skynet:categories:remove -n Cryptominer'
|
|
9
|
+
];
|
|
10
|
+
static flags = {
|
|
11
|
+
name: Flags.string({
|
|
12
|
+
name: 'name',
|
|
13
|
+
char: 'n',
|
|
14
|
+
description: 'The name of the category',
|
|
15
|
+
hasValue: true,
|
|
16
|
+
required: true
|
|
17
|
+
})
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const { flags } = await this.parse(RemoveCategory);
|
|
21
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
22
|
+
ux.action.start(`removing ${color.cyan(flags.name)} from categories`);
|
|
23
|
+
await skynet.removeCategory(flags.name);
|
|
24
|
+
ux.action.stop();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class Deprovision extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
user: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
category: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
notes: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
bypass: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
notify: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
'no-notify': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import SkynetAPI from '../lib/skynet.js';
|
|
2
|
+
import { requireSudo } from '../lib/sudo.js';
|
|
3
|
+
import { Command } from '@heroku-cli/command';
|
|
4
|
+
import { color } from '@heroku-cli/color';
|
|
5
|
+
import { Flags, ux } from '@oclif/core';
|
|
6
|
+
export default class Deprovision extends Command {
|
|
7
|
+
static description = '(requires sudo) suspends a user and deprovisions all addons';
|
|
8
|
+
static examples = [
|
|
9
|
+
'$ heroku skynet:deprovision -u foo@bar.com -n "helpful suspend message" -c "spam"'
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
user: Flags.string({
|
|
13
|
+
name: 'user',
|
|
14
|
+
char: 'u',
|
|
15
|
+
description: 'user to deprovision',
|
|
16
|
+
hasValue: true
|
|
17
|
+
}),
|
|
18
|
+
category: Flags.string({
|
|
19
|
+
name: 'category',
|
|
20
|
+
char: 'c',
|
|
21
|
+
description: 'suspension category',
|
|
22
|
+
hasValue: true,
|
|
23
|
+
required: true
|
|
24
|
+
}),
|
|
25
|
+
notes: Flags.string({
|
|
26
|
+
name: 'notes',
|
|
27
|
+
char: 'n',
|
|
28
|
+
description: 'suspend notes',
|
|
29
|
+
hasValue: true,
|
|
30
|
+
required: true
|
|
31
|
+
}),
|
|
32
|
+
bypass: Flags.boolean({
|
|
33
|
+
name: 'bypass',
|
|
34
|
+
description: 'force suspension, bypassing skynet safety checks',
|
|
35
|
+
required: false
|
|
36
|
+
}),
|
|
37
|
+
notify: Flags.boolean({
|
|
38
|
+
name: 'notify',
|
|
39
|
+
description: 'send user suspension email notification',
|
|
40
|
+
required: false
|
|
41
|
+
}),
|
|
42
|
+
'no-notify': Flags.boolean({
|
|
43
|
+
name: 'no-notify',
|
|
44
|
+
description: 'skip user suspension email notification',
|
|
45
|
+
required: false
|
|
46
|
+
})
|
|
47
|
+
};
|
|
48
|
+
async run() {
|
|
49
|
+
requireSudo();
|
|
50
|
+
const { flags } = await this.parse(Deprovision);
|
|
51
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
52
|
+
const user = flags.user || process.env.HEROKU_USER;
|
|
53
|
+
const notes = flags.notes;
|
|
54
|
+
const category = flags.category;
|
|
55
|
+
const force = flags.bypass || process.env.HEROKU_FORCE === '1';
|
|
56
|
+
// Handle notification logic inline since notifyOption was removed
|
|
57
|
+
let notify = true;
|
|
58
|
+
if (process.env.SKYNET_NOTIFICATION && process.env.SKYNET_NOTIFICATION === 'false') {
|
|
59
|
+
notify = false;
|
|
60
|
+
}
|
|
61
|
+
if (flags.notify && flags['no-notify']) {
|
|
62
|
+
throw new Error('Flag --notify and --no-notify could not use together');
|
|
63
|
+
}
|
|
64
|
+
if (flags['no-notify']) {
|
|
65
|
+
notify = false;
|
|
66
|
+
}
|
|
67
|
+
if (flags.notify) {
|
|
68
|
+
notify = true;
|
|
69
|
+
}
|
|
70
|
+
const notificationStatus = (notify) ? 'enabled' : 'disabled';
|
|
71
|
+
if (!user) {
|
|
72
|
+
throw new Error('Required flag: --user USER');
|
|
73
|
+
}
|
|
74
|
+
ux.action.start(`Deprovisioning ${color.cyan(user)}`);
|
|
75
|
+
let response = await skynet.deprovision(user, notes, category, notify, force);
|
|
76
|
+
ux.action.stop();
|
|
77
|
+
response = JSON.parse(response);
|
|
78
|
+
ux.log(`${color.cyan(response.status)}. ${response.message}. Notification ${notificationStatus}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class SuspendAppOwner extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
infile: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
category: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
notes: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
bypass: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
deprovision: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
async: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import SkynetAPI from '../../lib/skynet.js';
|
|
2
|
+
import * as utils from '../../lib/utils.js';
|
|
3
|
+
import { requireSudo } from '../../lib/sudo.js';
|
|
4
|
+
import { Command } from '@heroku-cli/command';
|
|
5
|
+
import { color } from '@heroku-cli/color';
|
|
6
|
+
import { Flags, ux } from '@oclif/core';
|
|
7
|
+
import { processSuspensionResult } from '../../lib/utils.js';
|
|
8
|
+
const chunkSize = 5;
|
|
9
|
+
export default class SuspendAppOwner extends Command {
|
|
10
|
+
static description = '(requires sudo) suspends the owner of a given app';
|
|
11
|
+
static examples = [
|
|
12
|
+
'$ heroku skynet:suspend:app-owner -a foobar -n "helpful suspend message" -c "ddos"'
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
app: Flags.string({
|
|
16
|
+
name: 'app',
|
|
17
|
+
char: 'a',
|
|
18
|
+
description: 'app that requires owner suspension',
|
|
19
|
+
hasValue: true
|
|
20
|
+
}),
|
|
21
|
+
infile: Flags.string({
|
|
22
|
+
name: 'infile',
|
|
23
|
+
char: 'i',
|
|
24
|
+
description: 'file of apps that require owner suspension',
|
|
25
|
+
hasValue: true
|
|
26
|
+
}),
|
|
27
|
+
category: Flags.string({
|
|
28
|
+
name: 'category',
|
|
29
|
+
char: 'c',
|
|
30
|
+
description: 'suspension category',
|
|
31
|
+
hasValue: true,
|
|
32
|
+
required: true
|
|
33
|
+
}),
|
|
34
|
+
notes: Flags.string({
|
|
35
|
+
name: 'notes',
|
|
36
|
+
char: 'n',
|
|
37
|
+
description: 'suspend notes',
|
|
38
|
+
hasValue: true,
|
|
39
|
+
required: true
|
|
40
|
+
}),
|
|
41
|
+
bypass: Flags.boolean({
|
|
42
|
+
name: 'bypass',
|
|
43
|
+
description: 'force suspension, bypassing skynet safety checks',
|
|
44
|
+
required: false
|
|
45
|
+
}),
|
|
46
|
+
deprovision: Flags.boolean({
|
|
47
|
+
name: 'deprovision',
|
|
48
|
+
char: 'd',
|
|
49
|
+
description: 'put user into the fast resource deletion flow',
|
|
50
|
+
required: false
|
|
51
|
+
}),
|
|
52
|
+
async: Flags.boolean({
|
|
53
|
+
name: 'async',
|
|
54
|
+
description: 'do not wait for suspension to complete',
|
|
55
|
+
required: false
|
|
56
|
+
})
|
|
57
|
+
};
|
|
58
|
+
async run() {
|
|
59
|
+
requireSudo();
|
|
60
|
+
const { flags } = await this.parse(SuspendAppOwner);
|
|
61
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
62
|
+
const app = flags.app;
|
|
63
|
+
const file = flags.infile;
|
|
64
|
+
const notes = flags.notes;
|
|
65
|
+
const category = flags.category;
|
|
66
|
+
const force = flags.bypass || process.env.HEROKU_FORCE === '1';
|
|
67
|
+
const deprovision = flags.deprovision;
|
|
68
|
+
if ((app && file) || (!app && !file)) {
|
|
69
|
+
ux.error('Either --app or --infile must be passed, but not both');
|
|
70
|
+
}
|
|
71
|
+
if (file) {
|
|
72
|
+
const apps = await utils.readlines(file);
|
|
73
|
+
const chunks = utils.arrayChunks(apps, chunkSize);
|
|
74
|
+
for (const chunk of chunks) {
|
|
75
|
+
ux.log('Suspending app owners: ' + chunk.join());
|
|
76
|
+
const response = await skynet.bulkSuspendAppOwner(apps.join(), notes, category, deprovision);
|
|
77
|
+
ux.log(`${color.cyan(response.status)}. ${response.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
ux.action.start(`Suspending app owner of ${color.cyan(app)}`);
|
|
82
|
+
let response = await skynet.suspendAppOwner(app, notes, category, force, deprovision, flags.async);
|
|
83
|
+
response = JSON.parse(response);
|
|
84
|
+
processSuspensionResult(app, response);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class SuspendApp extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
notes: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
category: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
bypass: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
async: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import SkynetAPI from '../../lib/skynet.js';
|
|
2
|
+
import { requireSudo } from '../../lib/sudo.js';
|
|
3
|
+
import { Command } from '@heroku-cli/command';
|
|
4
|
+
import { color } from '@heroku-cli/color';
|
|
5
|
+
import { Flags, ux } from '@oclif/core';
|
|
6
|
+
import { processSuspensionResult } from '../../lib/utils.js';
|
|
7
|
+
export default class SuspendApp extends Command {
|
|
8
|
+
static description = '(requires sudo) suspends an app';
|
|
9
|
+
static examples = [
|
|
10
|
+
'$ heroku skynet:suspend:app -a test-app -c "category" -n "helpful suspend message"'
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
app: Flags.string({
|
|
14
|
+
name: 'app',
|
|
15
|
+
char: 'a',
|
|
16
|
+
description: 'app to suspend',
|
|
17
|
+
hasValue: true,
|
|
18
|
+
required: true
|
|
19
|
+
}),
|
|
20
|
+
notes: Flags.string({
|
|
21
|
+
name: 'notes',
|
|
22
|
+
char: 'n',
|
|
23
|
+
description: 'suspend notes',
|
|
24
|
+
hasValue: true,
|
|
25
|
+
required: true
|
|
26
|
+
}),
|
|
27
|
+
category: Flags.string({
|
|
28
|
+
name: 'category',
|
|
29
|
+
char: 'c',
|
|
30
|
+
description: 'suspension category',
|
|
31
|
+
hasValue: true,
|
|
32
|
+
required: true
|
|
33
|
+
}),
|
|
34
|
+
bypass: Flags.boolean({
|
|
35
|
+
name: 'bypass',
|
|
36
|
+
description: 'force suspension, bypassing skynet safety checks',
|
|
37
|
+
required: false
|
|
38
|
+
}),
|
|
39
|
+
async: Flags.boolean({
|
|
40
|
+
name: 'async',
|
|
41
|
+
description: 'do not wait for suspension to complete',
|
|
42
|
+
required: false
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
requireSudo();
|
|
47
|
+
const { flags } = await this.parse(SuspendApp);
|
|
48
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
49
|
+
ux.action.start(color.blue.bold(`Suspending the app ${color.cyan(flags.app)}...`));
|
|
50
|
+
let response = await skynet.suspendApp(flags.app, flags.notes, flags.category, flags.bypass || process.env.HEROKU_FORCE === '1', flags.async);
|
|
51
|
+
response = JSON.parse(response);
|
|
52
|
+
processSuspensionResult(flags.app, response);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class SuspendUser extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
user: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
infile: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
category: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
notes: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
bypass: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
'no-notify': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
notify: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
13
|
+
deprovision: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
14
|
+
async: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import SkynetAPI from '../../lib/skynet.js';
|
|
2
|
+
import { requireSudo } from '../../lib/sudo.js';
|
|
3
|
+
import { Command } from '@heroku-cli/command';
|
|
4
|
+
import { Flags, ux } from '@oclif/core';
|
|
5
|
+
import { logError, processSuspensionResult } from '../../lib/utils.js';
|
|
6
|
+
import { color } from '@heroku-cli/color';
|
|
7
|
+
export default class SuspendUser extends Command {
|
|
8
|
+
static description = '(requires sudo) suspends a user';
|
|
9
|
+
static examples = [
|
|
10
|
+
'$ heroku sudo skynet:suspend:user -u foo@bar.com -n "helpful suspend message" -c "ddos"'
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
user: Flags.string({
|
|
14
|
+
name: 'user',
|
|
15
|
+
char: 'u',
|
|
16
|
+
description: 'user to suspend',
|
|
17
|
+
hasValue: true
|
|
18
|
+
}),
|
|
19
|
+
infile: Flags.string({
|
|
20
|
+
name: 'infile',
|
|
21
|
+
char: 'i',
|
|
22
|
+
description: 'file of users to suspend',
|
|
23
|
+
hasValue: true
|
|
24
|
+
}),
|
|
25
|
+
category: Flags.string({
|
|
26
|
+
name: 'category',
|
|
27
|
+
char: 'c',
|
|
28
|
+
description: 'suspension category',
|
|
29
|
+
hasValue: true,
|
|
30
|
+
required: true
|
|
31
|
+
}),
|
|
32
|
+
notes: Flags.string({
|
|
33
|
+
name: 'notes',
|
|
34
|
+
char: 'n',
|
|
35
|
+
description: 'suspend notes',
|
|
36
|
+
hasValue: true,
|
|
37
|
+
required: true
|
|
38
|
+
}),
|
|
39
|
+
bypass: Flags.boolean({
|
|
40
|
+
name: 'bypass',
|
|
41
|
+
description: 'force suspension, bypassing skynet safety checks',
|
|
42
|
+
required: false
|
|
43
|
+
}),
|
|
44
|
+
'no-notify': Flags.boolean({
|
|
45
|
+
name: 'no-notify',
|
|
46
|
+
description: 'skip user suspension email notification',
|
|
47
|
+
required: false
|
|
48
|
+
}),
|
|
49
|
+
notify: Flags.boolean({
|
|
50
|
+
name: 'notify',
|
|
51
|
+
description: 'send user suspension email notification',
|
|
52
|
+
required: false
|
|
53
|
+
}),
|
|
54
|
+
deprovision: Flags.boolean({
|
|
55
|
+
name: 'deprovision',
|
|
56
|
+
char: 'd',
|
|
57
|
+
description: 'put user into the fast resource deletion flow',
|
|
58
|
+
required: false
|
|
59
|
+
}),
|
|
60
|
+
async: Flags.boolean({
|
|
61
|
+
name: 'async',
|
|
62
|
+
description: 'do not wait for suspension to complete',
|
|
63
|
+
required: false
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
async run() {
|
|
67
|
+
requireSudo();
|
|
68
|
+
const { flags } = await this.parse(SuspendUser);
|
|
69
|
+
const skynet = new SkynetAPI(this.heroku.auth);
|
|
70
|
+
const user = flags.user || process.env.HEROKU_USER;
|
|
71
|
+
const file = flags.infile;
|
|
72
|
+
const notes = flags.notes;
|
|
73
|
+
const category = flags.category;
|
|
74
|
+
const force = flags.bypass || process.env.HEROKU_FORCE === '1';
|
|
75
|
+
const deprovision = flags.deprovision;
|
|
76
|
+
if (flags.notify && flags.no_notify) {
|
|
77
|
+
ux.error('Flag --notify and --no-notify cannot be used together');
|
|
78
|
+
}
|
|
79
|
+
// Start with default notification setting from environment variable
|
|
80
|
+
let notify = process.env.SKYNET_NOTIFICATION !== 'false';
|
|
81
|
+
// Handle explicit flags (--notify/--no-notify)
|
|
82
|
+
if (flags['no-notify']) {
|
|
83
|
+
notify = false;
|
|
84
|
+
}
|
|
85
|
+
else if (flags.notify) {
|
|
86
|
+
notify = true;
|
|
87
|
+
}
|
|
88
|
+
if ((user && file) || (!user && !file)) {
|
|
89
|
+
ux.error('Either --user USER or --infile must be passed, but not both');
|
|
90
|
+
}
|
|
91
|
+
if (file) {
|
|
92
|
+
ux.action.start(color.blue.bold('Starting bulk suspend...'));
|
|
93
|
+
let response = await skynet.bulkSuspendUsers(file, notes, category, notify, force, deprovision);
|
|
94
|
+
response = JSON.parse(response);
|
|
95
|
+
if (response.statusCode === 200) {
|
|
96
|
+
ux.action.stop(color.green(`Bulk suspended users from ${file}. Please check slack for update on processing.`));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
logError(response);
|
|
100
|
+
ux.action.stop(color.bgRed(`Failed to bulk suspend users from ${file}`));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
ux.action.start(color.blue.bold(`Suspending user ${color.cyan(user)}...`));
|
|
105
|
+
let response = await skynet.suspendUser(user, notes, category, notify, force, deprovision, flags.async);
|
|
106
|
+
response = JSON.parse(response);
|
|
107
|
+
processSuspensionResult(user, response);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class Suspensions extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
account: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|