@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.
Files changed (62) hide show
  1. package/dist/commands/allowlist/add.d.ts +11 -0
  2. package/dist/commands/allowlist/add.js +34 -0
  3. package/dist/commands/allowlist/remove.d.ts +10 -0
  4. package/dist/commands/allowlist/remove.js +27 -0
  5. package/dist/commands/allowlists.d.ts +7 -0
  6. package/dist/commands/allowlists.js +24 -0
  7. package/dist/commands/categories/add.d.ts +10 -0
  8. package/dist/commands/categories/add.js +33 -0
  9. package/dist/commands/categories/get.d.ts +7 -0
  10. package/dist/commands/categories/get.js +19 -0
  11. package/dist/commands/categories/remove.d.ts +9 -0
  12. package/dist/commands/categories/remove.js +26 -0
  13. package/dist/commands/deprovision.d.ts +14 -0
  14. package/dist/commands/deprovision.js +80 -0
  15. package/dist/commands/suspend/app-owner.d.ts +15 -0
  16. package/dist/commands/suspend/app-owner.js +87 -0
  17. package/dist/commands/suspend/apps.d.ts +13 -0
  18. package/dist/commands/suspend/apps.js +54 -0
  19. package/dist/commands/suspend/user.d.ts +17 -0
  20. package/dist/commands/suspend/user.js +110 -0
  21. package/dist/commands/suspensions.d.ts +9 -0
  22. package/dist/commands/suspensions.js +25 -0
  23. package/dist/commands/unsuspend/apps.d.ts +11 -0
  24. package/dist/commands/unsuspend/apps.js +48 -0
  25. package/dist/commands/unsuspend/user.d.ts +11 -0
  26. package/dist/commands/unsuspend/user.js +49 -0
  27. package/dist/commands/userpass/add.d.ts +10 -0
  28. package/dist/commands/userpass/add.js +33 -0
  29. package/dist/commands/userpass/remove.d.ts +10 -0
  30. package/dist/commands/userpass/remove.js +33 -0
  31. package/dist/lib/heroku.d.ts +13 -0
  32. package/dist/lib/heroku.js +32 -0
  33. package/dist/lib/skynet.d.ts +32 -0
  34. package/dist/lib/skynet.js +215 -0
  35. package/dist/lib/sudo.d.ts +1 -0
  36. package/dist/lib/sudo.js +7 -0
  37. package/dist/lib/utils.d.ts +5 -0
  38. package/dist/lib/utils.js +57 -0
  39. package/oclif.manifest.json +730 -0
  40. package/package.json +61 -23
  41. package/commands/allowlist/add.js +0 -38
  42. package/commands/allowlist/remove.js +0 -29
  43. package/commands/allowlists.js +0 -30
  44. package/commands/categories/add.js +0 -40
  45. package/commands/categories/get.js +0 -27
  46. package/commands/categories/remove.js +0 -33
  47. package/commands/deprovision.js +0 -42
  48. package/commands/suspend/app-owner.js +0 -58
  49. package/commands/suspend/apps.js +0 -34
  50. package/commands/suspend/user.js +0 -80
  51. package/commands/suspensions.js +0 -24
  52. package/commands/unsuspend/apps.js +0 -33
  53. package/commands/unsuspend/user.js +0 -33
  54. package/commands/userpass/add.js +0 -21
  55. package/commands/userpass/remove.js +0 -21
  56. package/index.js +0 -23
  57. package/lib/command.js +0 -12
  58. package/lib/heroku.js +0 -30
  59. package/lib/notifyOption.js +0 -24
  60. package/lib/skynet.js +0 -218
  61. package/lib/sudo.js +0 -5
  62. 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,7 @@
1
+ import { Command } from '@heroku-cli/command';
2
+ export default class Allowlists extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {};
6
+ run(): Promise<void>;
7
+ }
@@ -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,7 @@
1
+ import { Command } from '@heroku-cli/command';
2
+ export default class GetCategories extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {};
6
+ run(): Promise<void>;
7
+ }
@@ -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
+ }