clever-tools 4.10.0 → 5.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 (58) hide show
  1. package/bin/clever.js +5 -10
  2. package/package.json +27 -27
  3. package/src/clever-client/drains.js +17 -0
  4. package/src/commands/accesslogs/accesslogs.command.js +35 -18
  5. package/src/commands/accesslogs/accesslogs.docs.md +1 -1
  6. package/src/commands/addon/addon.docs.md +2 -2
  7. package/src/commands/config-provider/config-provider.docs.md +5 -5
  8. package/src/commands/create/create.command.js +8 -1
  9. package/src/commands/database/database.docs.md +2 -2
  10. package/src/commands/deploy/deploy.docs.md +7 -6
  11. package/src/commands/drain/drain.args.js +6 -0
  12. package/src/commands/drain/drain.check.command.js +43 -0
  13. package/src/commands/drain/drain.create.betterstack.command.js +36 -0
  14. package/src/commands/drain/drain.create.command.js +0 -118
  15. package/src/commands/drain/drain.create.datadog.command.js +27 -0
  16. package/src/commands/drain/drain.create.elasticsearch.command.js +48 -0
  17. package/src/commands/drain/drain.create.newrelic.command.js +36 -0
  18. package/src/commands/drain/drain.create.ovh-tcp.command.js +29 -0
  19. package/src/commands/drain/drain.create.raw-http.command.js +30 -0
  20. package/src/commands/drain/drain.create.splunk.command.js +61 -0
  21. package/src/commands/drain/drain.create.syslog-tcp.command.js +29 -0
  22. package/src/commands/drain/drain.create.syslog-udp.command.js +29 -0
  23. package/src/commands/drain/drain.docs.md +296 -7
  24. package/src/commands/drain/drain.options.js +26 -0
  25. package/src/commands/features/features.list.command.js +6 -13
  26. package/src/commands/global.commands.js +25 -1
  27. package/src/commands/global.options.js +8 -0
  28. package/src/commands/k8s/k8s.docs.md +17 -17
  29. package/src/commands/keycloak/keycloak.docs.md +11 -11
  30. package/src/commands/kv/kv.docs.md +1 -1
  31. package/src/commands/link/link.docs.md +1 -1
  32. package/src/commands/login/login.command.js +11 -1
  33. package/src/commands/matomo/matomo.docs.md +6 -6
  34. package/src/commands/metabase/metabase.docs.md +9 -9
  35. package/src/commands/ng/ng.docs.md +10 -10
  36. package/src/commands/oauth-consumers/oauth-consumers.docs.md +4 -4
  37. package/src/commands/otoroshi/otoroshi.docs.md +13 -13
  38. package/src/commands/profile/profile.switch.command.js +5 -1
  39. package/src/commands/service/service.docs.md +4 -4
  40. package/src/commands/ssh/ssh.command.js +7 -1
  41. package/src/config/features.js +61 -29
  42. package/src/format-table.js +14 -10
  43. package/src/initial-setup.js +13 -0
  44. package/src/initial-update-notifier.js +2 -5
  45. package/src/lib/access-logs-clf.js +52 -0
  46. package/src/lib/access-logs-transport.js +29 -0
  47. package/src/lib/cliparse-patched.js +19 -1
  48. package/src/lib/define-command.types.d.ts +7 -2
  49. package/src/lib/k8s.js +1 -0
  50. package/src/lib/operator-commands.js +1 -0
  51. package/src/lib/profile.js +8 -1
  52. package/src/lib/prompts.js +30 -2
  53. package/src/models/drain.js +39 -12
  54. package/src/models/git-isomorphic.js +16 -1
  55. package/src/models/git-system.js +2 -2
  56. package/src/models/git.js +19 -1
  57. package/src/models/oauth-consumer.js +1 -1
  58. package/src/models/send-to-api.js +15 -0
package/bin/clever.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  verboseOption,
15
15
  versionOption,
16
16
  } from '../src/commands/global.options.js';
17
- import { EXPERIMENTAL_FEATURES, getFeatures } from '../src/config/features.js';
17
+ import { EXPERIMENTAL_FEATURES, isFeatureEnabled } from '../src/config/features.js';
18
18
  import { cliparse } from '../src/lib/cliparse-patched.js';
19
19
  import { styleText } from '../src/lib/style-text.js';
20
20
  import { getDefault, getEnumValues, isBoolean, isRequired } from '../src/lib/zod-utils.js';
@@ -47,14 +47,10 @@ if (process.argv[2] === 'curl') {
47
47
  }
48
48
 
49
49
  async function run() {
50
- // Get enabled experimental features
51
- /** @type {Record<string, boolean>} */
52
- const featuresFromConf = await getFeatures();
53
-
54
50
  // Build all commands from globalCommands
55
51
  const commands = [];
56
52
  for (const [name, entry] of /** @type {[string, CommandEntry][]} */ (Object.entries(globalCommands))) {
57
- const command = buildCommand(name, entry, featuresFromConf);
53
+ const command = buildCommand(name, entry);
58
54
  if (command != null) {
59
55
  commands.push(command);
60
56
  }
@@ -96,10 +92,9 @@ async function run() {
96
92
  * Recursively build commands from the global commands structure
97
93
  * @param {string} name - Command name
98
94
  * @param {CommandEntry} commandEntry - Command entry (either a command object or [command, subcommands])
99
- * @param {Record<string, boolean>} featuresFromConf - Enabled features configuration
100
95
  * @returns {Object|null} cliparse command or null if filtered out
101
96
  */
102
- function buildCommand(name, commandEntry, featuresFromConf) {
97
+ function buildCommand(name, commandEntry) {
103
98
  /** @type {CommandDefinition} */
104
99
  let commandDef;
105
100
  /** @type {Record<string, CommandEntry>} */
@@ -113,14 +108,14 @@ function buildCommand(name, commandEntry, featuresFromConf) {
113
108
  }
114
109
 
115
110
  // Check if this is an experimental feature that needs to be enabled
116
- if (commandDef.featureFlag && !featuresFromConf[commandDef.featureFlag]) {
111
+ if (commandDef.featureFlag && !isFeatureEnabled(commandDef.featureFlag)) {
117
112
  return null;
118
113
  }
119
114
 
120
115
  // Build subcommands recursively
121
116
  const subcommands = [];
122
117
  for (const [subName, subEntry] of Object.entries(subcommandsMap)) {
123
- const subcommand = buildCommand(subName, subEntry, featuresFromConf);
118
+ const subcommand = buildCommand(subName, subEntry);
124
119
  if (subcommand != null) {
125
120
  subcommands.push(subcommand);
126
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "4.10.0",
3
+ "version": "5.0.0",
4
4
  "description": "Command Line Interface for Clever Cloud.",
5
5
  "keywords": [
6
6
  "cli",
@@ -49,56 +49,56 @@
49
49
  "typecheck": "tsc -p tsconfig.json"
50
50
  },
51
51
  "dependencies": {
52
- "@babel/traverse": "7.28.5",
53
- "@clevercloud/client": "12.0.0",
54
- "@inquirer/prompts": "7.8.4",
52
+ "@clevercloud/client": "12.6.3",
53
+ "@inquirer/prompts": "8.7.2",
55
54
  "char-regex": "2.0.2",
56
55
  "cliparse": "0.5.0",
57
- "dedent": "1.7.0",
56
+ "dedent": "1.7.2",
58
57
  "eventsource": "4.1.0",
59
58
  "ioredis": "5.8.2",
60
- "iso8601-duration": "2.1.3",
61
- "isomorphic-git": "1.35.1",
59
+ "iso8601-duration": "2.1.4",
60
+ "isomorphic-git": "1.41.9",
62
61
  "linux-release-info": "3.0.0",
63
- "lodash": "4.17.21",
64
- "open": "10.2.0",
65
- "semver": "7.7.3",
66
- "simple-git": "3.30.0",
67
- "slugify": "1.6.6",
68
- "tldts": "7.0.19",
62
+ "lodash": "4.18.1",
63
+ "open": "11.0.2",
64
+ "semver": "7.8.5",
65
+ "simple-git": "3.36.0",
66
+ "slugify": "1.6.9",
67
+ "tldts": "7.4.12",
68
+ "undici": "8.10.2",
69
69
  "update-notifier": "7.3.1",
70
70
  "xdg": "0.1.1",
71
- "zod": "4.1.13"
71
+ "zod": "4.5.4"
72
72
  },
73
73
  "devDependencies": {
74
- "@aws-sdk/client-s3": "3.940.0",
74
+ "@aws-sdk/client-s3": "3.1128.0",
75
75
  "@clevercloud/eslint-config": "1.1.0",
76
- "@commitlint/cli": "19.8.1",
77
- "@commitlint/config-conventional": "19.8.1",
78
- "@eslint/compat": "1.3.2",
79
- "@rollup/plugin-commonjs": "28.0.6",
76
+ "@commitlint/cli": "21.2.2",
77
+ "@commitlint/config-conventional": "21.2.2",
78
+ "@eslint/compat": "2.1.1",
79
+ "@rollup/plugin-commonjs": "29.0.3",
80
80
  "@rollup/plugin-json": "6.1.0",
81
81
  "@rollup/plugin-node-resolve": "16.0.3",
82
82
  "@types/mime-types": "3.0.1",
83
- "@types/picomatch": "4.0.2",
84
- "@yao-pkg/pkg": "6.10.1",
83
+ "@types/picomatch": "4.0.3",
84
+ "@yao-pkg/pkg": "6.22.0",
85
85
  "eslint": "9.39.1",
86
- "globals": "16.5.0",
86
+ "globals": "17.12.0",
87
87
  "mime-types": "3.0.2",
88
- "prettier": "3.7.3",
88
+ "prettier": "3.9.6",
89
89
  "prettier-plugin-organize-imports": "4.3.0",
90
90
  "remark-parse": "11.0.0",
91
91
  "remark-stringify": "11.0.0",
92
- "rollup": "4.53.3",
93
- "tinyglobby": "0.2.15",
92
+ "rollup": "4.63.1",
93
+ "tinyglobby": "0.2.17",
94
94
  "typescript": "5.9.3",
95
95
  "unified": "11.0.5"
96
96
  },
97
97
  "engines": {
98
- "node": ">=22"
98
+ "node": ">=24"
99
99
  },
100
100
  "volta": {
101
- "node": "22.17.0",
101
+ "node": "24.18.1",
102
102
  "npm": "11.6.2"
103
103
  }
104
104
  }
@@ -108,3 +108,20 @@ export function enableDrain(params) {
108
108
  // no body
109
109
  });
110
110
  }
111
+
112
+ /**
113
+ * POST /v4/drains/organisations/{ownerId}/resources/{resourceId}/drains/{drainId}/check
114
+ * @param {Object} params
115
+ * @param {String} params.ownerId
116
+ * @param {String} params.resourceId
117
+ * @param {String} params.drainId
118
+ */
119
+ export function checkDrain(params) {
120
+ // no multipath for /self or /organisations/{id}
121
+ return Promise.resolve({
122
+ method: 'post',
123
+ url: `/v4/drains/organisations/${params.ownerId}/resources/${params.resourceId}/drains/${params.drainId}/check`,
124
+ headers: { Accept: 'application/json' },
125
+ // no body
126
+ });
127
+ }
@@ -1,5 +1,7 @@
1
1
  import { ApplicationAccessLogStream } from '@clevercloud/client/esm/streams/access-logs.js';
2
2
  import { formatTable } from '../../format-table.js';
3
+ import { formatClf } from '../../lib/access-logs-clf.js';
4
+ import { guessAccessLogTransport } from '../../lib/access-logs-transport.js';
3
5
  import { defineCommand } from '../../lib/define-command.js';
4
6
  import { styleText } from '../../lib/style-text.js';
5
7
  import { Logger } from '../../logger.js';
@@ -8,12 +10,12 @@ import { JsonArray } from '../../models/json-array.js';
8
10
  import { getHostAndTokens } from '../../models/send-to-api.js';
9
11
  import { truncateWithEllipsis } from '../../models/utils.js';
10
12
  import {
13
+ accessLogsFormatOption,
11
14
  addonIdOrRealIdOption,
12
15
  afterOption,
13
16
  aliasOption,
14
17
  appIdOrNameOption,
15
18
  beforeOption,
16
- logsFormatOption,
17
19
  } from '../global.options.js';
18
20
 
19
21
  const THROTTLE_ELEMENTS = 2000;
@@ -22,28 +24,40 @@ const THROTTLE_PER_IN_MILLISECONDS = 100;
22
24
 
23
25
  const CITY_MAX_LENGTH = 20;
24
26
 
27
+ /**
28
+ * Format an access log as a human readable table row.
29
+ *
30
+ * Non-HTTP access logs (TCP redirections, SSH connections, or HTTP connections cut before the
31
+ * proxy could answer) have no `http` section: their status and request columns are omitted.
32
+ *
33
+ * @param {object} log an access log, as emitted by `ApplicationAccessLogStream`
34
+ * @returns {string}
35
+ */
25
36
  function formatHuman(log) {
26
37
  const { date, http, source } = log;
27
38
  const country = source.countryCode ?? '(unknown)';
28
39
  const hasSourceCity = source.city ?? '';
29
40
 
30
- return formatTable(
31
- [
32
- [
33
- styleText('grey', date.toISOString(date)),
34
- source.ip,
35
- `${country}${hasSourceCity ? '/' + truncateWithEllipsis(CITY_MAX_LENGTH, source.city) : ''}`,
36
- colorStatusCode(http.response.statusCode),
37
- http.request.method.toString().padEnd(4, ' ') + ' ' + http.request.path,
38
- ],
39
- ],
40
-
41
- ACCESSLOG_COLUMN_WIDTHS,
42
- );
41
+ const columns = [
42
+ styleText('grey', date.toISOString(date)),
43
+ guessAccessLogTransport(log),
44
+ source.ip,
45
+ `${country}${hasSourceCity ? '/' + truncateWithEllipsis(CITY_MAX_LENGTH, source.city) : ''}`,
46
+ ];
47
+
48
+ if (http != null) {
49
+ columns.push(colorStatusCode(http.response.statusCode));
50
+ columns.push(http.request.method);
51
+ columns.push(http.request.path);
52
+ }
53
+
54
+ return formatTable([columns], ACCESSLOG_COLUMN_WIDTHS);
43
55
  }
44
56
 
45
57
  const ACCESSLOG_COLUMN_WIDTHS = [
46
58
  '2024-06-24T08:05:43.880Z',
59
+ // longest transport name
60
+ 'HTTP',
47
61
  '255.255.255.255',
48
62
  // country / city
49
63
  2 + 1 + CITY_MAX_LENGTH,
@@ -76,7 +90,7 @@ export const accesslogsCommand = defineCommand({
76
90
  options: {
77
91
  alias: aliasOption,
78
92
  app: appIdOrNameOption,
79
- format: logsFormatOption,
93
+ format: accessLogsFormatOption,
80
94
  before: beforeOption,
81
95
  after: afterOption,
82
96
  addon: addonIdOrRealIdOption,
@@ -132,13 +146,16 @@ export const accesslogsCommand = defineCommand({
132
146
  case 'json-stream':
133
147
  Logger.printJson(log);
134
148
  break;
135
- case 'human':
136
- default:
137
- // when the connection is cut too early, or for TCP redirections, we don't have HTTP section
149
+ case 'clf':
150
+ // CLF only describes HTTP requests, so logs without an HTTP section are skipped
138
151
  if (log.http == null) {
139
152
  break;
140
153
  }
141
154
 
155
+ Logger.println(formatClf(log));
156
+ break;
157
+ case 'human':
158
+ default:
142
159
  Logger.println(formatHuman(log));
143
160
  break;
144
161
  }
@@ -17,4 +17,4 @@ clever accesslogs [options]
17
17
  |`-a`, `--alias` `<alias>`|Short name for the application|
18
18
  |`--app` `<app-id\|app-name>`|Application to manage by its ID (or name, if unambiguous)|
19
19
  |`--before`, `--until` `<before>`|Fetch logs before this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h)|
20
- |`-F`, `--format` `<format>`|Output format (human, json, json-stream) (default: human)|
20
+ |`-F`, `--format` `<format>`|Output format (clf only outputs HTTP access logs) (human, json, json-stream, clf) (default: human)|
@@ -55,7 +55,7 @@ clever addon delete <addon-id|addon-name> [options]
55
55
 
56
56
  |Name|Description|
57
57
  |---|---|
58
- |`addon-id|addon-name`|Add-on ID (or name, if unambiguous)|
58
+ |`addon-id\|addon-name`|Add-on ID (or name, if unambiguous)|
59
59
 
60
60
  ### ⚙️ Options
61
61
 
@@ -148,7 +148,7 @@ clever addon rename <addon-id|addon-name> <addon-name> [options]
148
148
 
149
149
  |Name|Description|
150
150
  |---|---|
151
- |`addon-id|addon-name`|Add-on ID (or name, if unambiguous)|
151
+ |`addon-id\|addon-name`|Add-on ID (or name, if unambiguous)|
152
152
  |`addon-name`|Add-on name|
153
153
 
154
154
  ### ⚙️ Options
@@ -20,7 +20,7 @@ clever config-provider get <addon-id|config-provider-id|addon-name> [options]
20
20
 
21
21
  |Name|Description|
22
22
  |---|---|
23
- |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
23
+ |`addon-id\|config-provider-id\|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
24
24
 
25
25
  ### ⚙️ Options
26
26
 
@@ -41,7 +41,7 @@ clever config-provider import <addon-id|config-provider-id|addon-name> [options]
41
41
 
42
42
  |Name|Description|
43
43
  |---|---|
44
- |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
44
+ |`addon-id\|config-provider-id\|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
45
45
 
46
46
  ### ⚙️ Options
47
47
 
@@ -75,7 +75,7 @@ clever config-provider open <addon-id|config-provider-id|addon-name>
75
75
 
76
76
  |Name|Description|
77
77
  |---|---|
78
- |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
78
+ |`addon-id\|config-provider-id\|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
79
79
 
80
80
  ## ➡️ `clever config-provider rm` <kbd>Since 4.6.0</kbd>
81
81
 
@@ -89,7 +89,7 @@ clever config-provider rm <addon-id|config-provider-id|addon-name> <variable-nam
89
89
 
90
90
  |Name|Description|
91
91
  |---|---|
92
- |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
92
+ |`addon-id\|config-provider-id\|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
93
93
  |`variable-name`|Name of the environment variable|
94
94
 
95
95
  ## ➡️ `clever config-provider set` <kbd>Since 4.6.0</kbd>
@@ -104,6 +104,6 @@ clever config-provider set <addon-id|config-provider-id|addon-name> <variable-na
104
104
 
105
105
  |Name|Description|
106
106
  |---|---|
107
- |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
107
+ |`addon-id\|config-provider-id\|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
108
108
  |`variable-name`|Name of the environment variable|
109
109
  |`variable-value`|Value of the environment variable|
@@ -9,6 +9,7 @@ import { Logger } from '../../logger.js';
9
9
  import * as AppConfig from '../../models/app_configuration.js';
10
10
  import * as Application from '../../models/application.js';
11
11
  import { AVAILABLE_ZONES, listAvailableTypes, listAvailableZones } from '../../models/application.js';
12
+ import { LinkedWorktreeNotSupportedError } from '../../models/git-isomorphic.js';
12
13
  import { Git } from '../../models/git.js';
13
14
  import { aliasCreationOption, humanJsonOutputFormatOption, orgaIdOrNameOption } from '../global.options.js';
14
15
 
@@ -54,7 +55,13 @@ async function displayAppCreation(app, alias, github, taskCommand) {
54
55
  Logger.println(` ${shellCommand('git commit -m "Initial commit"')}`);
55
56
  Logger.println();
56
57
  } else {
57
- const isClean = await git.isGitWorkingDirectoryClean();
58
+ // Best-effort hint: if we can't tell because of a linked worktree on the JS backend, don't nag
59
+ const isClean = await git.isGitWorkingDirectoryClean().catch((error) => {
60
+ if (error instanceof LinkedWorktreeNotSupportedError) {
61
+ return true;
62
+ }
63
+ throw error;
64
+ });
58
65
  if (!isClean) {
59
66
  Logger.println(` ${styleText('yellow', '!')} Commit your changes first:`);
60
67
  Logger.println(` ${shellCommand('git add .')}`);
@@ -20,7 +20,7 @@ clever database backups <database-id|addon-id> [options]
20
20
 
21
21
  |Name|Description|
22
22
  |---|---|
23
- |`database-id|addon-id`|Any database ID (format: addon_UUID, postgresql_UUID, mysql_UUID, ...)|
23
+ |`database-id\|addon-id`|Any database ID (format: addon_UUID, postgresql_UUID, mysql_UUID, ...)|
24
24
 
25
25
  ### ⚙️ Options
26
26
 
@@ -41,7 +41,7 @@ clever database backups download <database-id|addon-id> <backup-id> [options]
41
41
 
42
42
  |Name|Description|
43
43
  |---|---|
44
- |`database-id|addon-id`|Any database ID (format: addon_UUID, postgresql_UUID, mysql_UUID, ...)|
44
+ |`database-id\|addon-id`|Any database ID (format: addon_UUID, postgresql_UUID, mysql_UUID, ...)|
45
45
  |`backup-id`|A Database backup ID (format: UUID)|
46
46
 
47
47
  ### ⚙️ Options
@@ -23,20 +23,21 @@ clever deploy [options]
23
23
 
24
24
  ### 🧪 Experimental: System git backend
25
25
 
26
- Clever Tools uses a current JS implementation for git operations. This works without requiring git to be installed on your system, but has some limitations:
26
+ Clever Tools uses the `git` command installed on your system for git operations (it must be in your `PATH` environment variable).
27
+
28
+ If `git` is not available, or you experience an issue with this backend, you can fall back to the previous pure JS implementation. This works without requiring git to be installed on your system, but has some limitations:
27
29
 
28
30
  * **HTTP-only**: cannot use SSH-based git protocols
29
31
  * **Slow performance** on repositories with rewritten history (rebases, squashes)
30
32
  * **Connection timeouts** on large repositories or when pushing big files, due to HTTP-based transfers
31
-
32
- If you experience any of these issues, you can enable the **system git backend** which uses the `git` command installed on your system (it must be in your `PATH` environment variable).
33
+ * **No git worktree support**: deploying from a linked git worktree (`git worktree add`) fails with `Could not find HEAD`
33
34
 
34
35
  ```bash
35
- clever features enable system-git
36
+ clever features disable system-git
36
37
  ```
37
38
 
38
- To disable and return to the current JS implementation:
39
+ To switch back to the system git backend:
39
40
 
40
41
  ```bash
41
- clever features disable system-git
42
+ clever features enable system-git
42
43
  ```
@@ -6,3 +6,9 @@ export const drainIdArg = defineArgument({
6
6
  description: 'Drain ID',
7
7
  placeholder: 'drain-id',
8
8
  });
9
+
10
+ export const drainUrlArg = defineArgument({
11
+ schema: z.string(),
12
+ description: 'Drain URL',
13
+ placeholder: 'drain-url',
14
+ });
@@ -0,0 +1,43 @@
1
+ import { checkDrain } from '../../clever-client/drains.js';
2
+ import { defineCommand } from '../../lib/define-command.js';
3
+ import { styleText } from '../../lib/style-text.js';
4
+ import { Logger } from '../../logger.js';
5
+ import { resolveDrainResource } from '../../models/drain.js';
6
+ import { sendToApi } from '../../models/send-to-api.js';
7
+ import {
8
+ addonIdOrRealIdOption,
9
+ aliasOption,
10
+ appIdOrNameOption,
11
+ humanJsonOutputFormatOption,
12
+ } from '../global.options.js';
13
+ import { drainIdArg } from './drain.args.js';
14
+
15
+ export const drainCheckCommand = defineCommand({
16
+ description: "Check that a drain's recipient is reachable and accepts deliveries",
17
+ since: '4.11.0',
18
+ options: {
19
+ alias: aliasOption,
20
+ appIdOrName: appIdOrNameOption,
21
+ addonIdOrRealId: addonIdOrRealIdOption,
22
+ format: humanJsonOutputFormatOption,
23
+ },
24
+ args: [drainIdArg],
25
+ async handler(options, drainId) {
26
+ const { alias, appIdOrName, addonIdOrRealId, format } = options;
27
+ const { ownerId, resourceId } = await resolveDrainResource(alias, appIdOrName, addonIdOrRealId);
28
+
29
+ const probe = await checkDrain({ ownerId, resourceId, drainId }).then(sendToApi);
30
+ switch (format) {
31
+ case 'json': {
32
+ Logger.printJson({ ok: probe.ok, message: probe.message });
33
+ break;
34
+ }
35
+ case 'human':
36
+ default: {
37
+ const status = probe.ok ? styleText(['bold', 'green'], 'OK') : styleText(['bold', 'red'], 'FAILED');
38
+ Logger.println(`Probe: ${status}`);
39
+ Logger.println(probe.message);
40
+ }
41
+ }
42
+ },
43
+ });
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import { defineCommand } from '../../lib/define-command.js';
3
+ import { defineOption } from '../../lib/define-option.js';
4
+ import { styleText } from '../../lib/style-text.js';
5
+ import { Logger } from '../../logger.js';
6
+ import { createLogDrain, resolveDrainResource } from '../../models/drain.js';
7
+ import { addonIdOrRealIdOption, aliasOption, appIdOrNameOption } from '../global.options.js';
8
+ import { drainUrlArg } from './drain.args.js';
9
+
10
+ export const drainCreateBetterstackCommand = defineCommand({
11
+ description: 'Create a Better Stack drain',
12
+ since: '4.11.0',
13
+ options: {
14
+ sourceToken: defineOption({
15
+ name: 'source-token',
16
+ schema: z.string().min(1),
17
+ description: 'Source token',
18
+ aliases: ['t'],
19
+ placeholder: 'source-token',
20
+ }),
21
+ alias: aliasOption,
22
+ appIdOrName: appIdOrNameOption,
23
+ addonIdOrRealId: addonIdOrRealIdOption,
24
+ },
25
+ args: [drainUrlArg],
26
+ async handler(options, url) {
27
+ const { alias, appIdOrName, addonIdOrRealId, sourceToken } = options;
28
+ const { ownerId, resourceId } = await resolveDrainResource(alias, appIdOrName, addonIdOrRealId);
29
+
30
+ const drain = await createLogDrain('BETTERSTACK', ownerId, resourceId, url, { sourceToken });
31
+
32
+ Logger.printSuccess(
33
+ `Better Stack drain ${styleText(['bold', 'green'], drain.id)} has been successfully created and enabled!`,
34
+ );
35
+ },
36
+ });
@@ -1,124 +1,6 @@
1
- import { z } from 'zod';
2
- import { createDrain } from '../../clever-client/drains.js';
3
- import { defineArgument } from '../../lib/define-argument.js';
4
1
  import { defineCommand } from '../../lib/define-command.js';
5
- import { defineOption } from '../../lib/define-option.js';
6
- import { styleText } from '../../lib/style-text.js';
7
- import { Logger } from '../../logger.js';
8
- import { DRAIN_TYPE_CLI_CODES, DRAIN_TYPES, resolveDrainResource } from '../../models/drain.js';
9
- import { sendToApi } from '../../models/send-to-api.js';
10
- import { addonIdOrRealIdOption, aliasOption, appIdOrNameOption } from '../global.options.js';
11
2
 
12
3
  export const drainCreateCommand = defineCommand({
13
4
  description: 'Create a drain',
14
5
  since: '0.9.0',
15
- options: {
16
- username: defineOption({
17
- name: 'username',
18
- schema: z.string().optional(),
19
- description: 'Basic auth username (for elasticsearch or raw-http)',
20
- aliases: ['u'],
21
- placeholder: 'username',
22
- }),
23
- password: defineOption({
24
- name: 'password',
25
- schema: z.string().optional(),
26
- description: 'Basic auth password (for elasticsearch or raw-http)',
27
- aliases: ['p'],
28
- placeholder: 'password',
29
- }),
30
- apiKey: defineOption({
31
- name: 'api-key',
32
- schema: z.string().optional(),
33
- description: 'API key (for newrelic)',
34
- aliases: ['k'],
35
- placeholder: 'api-key',
36
- }),
37
- indexPrefix: defineOption({
38
- name: 'index-prefix',
39
- schema: z.string().optional(),
40
- description: 'Optional index prefix (for elasticsearch), `logstash` value is used if not set',
41
- aliases: ['i'],
42
- placeholder: 'index-prefix',
43
- }),
44
- rfc5424StructuredDataParameters: defineOption({
45
- name: 'sd-params',
46
- schema: z.string().optional(),
47
- description: 'RFC5424 structured data parameters (for ovh-tcp), e.g.: `X-OVH-TOKEN=\\\"REDACTED\\\"`',
48
- aliases: ['s'],
49
- placeholder: 'sd-params',
50
- }),
51
- alias: aliasOption,
52
- appIdOrName: appIdOrNameOption,
53
- addonIdOrRealId: addonIdOrRealIdOption,
54
- },
55
- args: [
56
- defineArgument({
57
- schema: z.enum(DRAIN_TYPE_CLI_CODES),
58
- description: 'Drain type',
59
- placeholder: 'drain-type',
60
- }),
61
- defineArgument({
62
- schema: z.string(),
63
- description: 'Drain URL',
64
- placeholder: 'drain-url',
65
- }),
66
- ],
67
- async handler(options, drainTypeCliCode, url) {
68
- const { alias, appIdOrName, addonIdOrRealId } = options;
69
- const { username, password, apiKey, indexPrefix, rfc5424StructuredDataParameters } = options;
70
-
71
- const drainType = Object.values(DRAIN_TYPES).find((drainType) => drainType.cliCode === drainTypeCliCode);
72
-
73
- const { ownerId, resourceId } = await resolveDrainResource(alias, appIdOrName, addonIdOrRealId);
74
-
75
- const body = {
76
- kind: 'LOG',
77
- recipient: {
78
- type: drainType.apiCode,
79
- url,
80
- },
81
- };
82
-
83
- if (drainTypeCliCode === DRAIN_TYPES.ELASTICSEARCH.cliCode) {
84
- if (!indexPrefix) {
85
- throw new Error(
86
- `${DRAIN_TYPES.ELASTICSEARCH.cliCode} drains require an index prefix (--index-prefix) to be set`,
87
- );
88
- }
89
- if (!url.endsWith('/_bulk')) {
90
- throw new Error(`${DRAIN_TYPES.ELASTICSEARCH.cliCode} drain URL must end with '/_bulk'`);
91
- }
92
- body.recipient.index = indexPrefix;
93
- }
94
-
95
- if (drainTypeCliCode === DRAIN_TYPES.ELASTICSEARCH.cliCode || drainTypeCliCode === DRAIN_TYPES.RAW_HTTP.cliCode) {
96
- if (username) {
97
- body.recipient.username = username;
98
- }
99
- if (password) {
100
- body.recipient.password = password;
101
- }
102
- }
103
-
104
- if (drainTypeCliCode === DRAIN_TYPES.NEWRELIC.cliCode) {
105
- if (!apiKey) {
106
- throw new Error(`${DRAIN_TYPES.NEWRELIC.cliCode} drains require an API key (--api-key) to be set`);
107
- }
108
- body.recipient.apiKey = apiKey;
109
- }
110
-
111
- if (
112
- drainTypeCliCode === DRAIN_TYPES.OVH_TCP.cliCode ||
113
- drainTypeCliCode === DRAIN_TYPES.SYSLOG_TCP.cliCode ||
114
- drainTypeCliCode === DRAIN_TYPES.SYSLOG_UDP.cliCode
115
- ) {
116
- if (rfc5424StructuredDataParameters) {
117
- body.recipient.rfc5424StructuredDataParameters = rfc5424StructuredDataParameters;
118
- }
119
- }
120
-
121
- const drain = await createDrain({ ownerId, resourceId, body }).then(sendToApi);
122
- Logger.printSuccess(`Drain ${styleText(['bold', 'green'], drain.id)} has been successfully created and enabled!`);
123
- },
124
6
  });
@@ -0,0 +1,27 @@
1
+ import { defineCommand } from '../../lib/define-command.js';
2
+ import { styleText } from '../../lib/style-text.js';
3
+ import { Logger } from '../../logger.js';
4
+ import { createLogDrain, resolveDrainResource } from '../../models/drain.js';
5
+ import { addonIdOrRealIdOption, aliasOption, appIdOrNameOption } from '../global.options.js';
6
+ import { drainUrlArg } from './drain.args.js';
7
+
8
+ export const drainCreateDatadogCommand = defineCommand({
9
+ description: 'Create a Datadog drain',
10
+ since: '0.9.0',
11
+ options: {
12
+ alias: aliasOption,
13
+ appIdOrName: appIdOrNameOption,
14
+ addonIdOrRealId: addonIdOrRealIdOption,
15
+ },
16
+ args: [drainUrlArg],
17
+ async handler(options, url) {
18
+ const { alias, appIdOrName, addonIdOrRealId } = options;
19
+ const { ownerId, resourceId } = await resolveDrainResource(alias, appIdOrName, addonIdOrRealId);
20
+
21
+ const drain = await createLogDrain('DATADOG', ownerId, resourceId, url);
22
+
23
+ Logger.printSuccess(
24
+ `Datadog drain ${styleText(['bold', 'green'], drain.id)} has been successfully created and enabled!`,
25
+ );
26
+ },
27
+ });