clever-tools 4.1.0 → 4.3.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/bin/clever.js +106 -20
- package/package.json +1 -1
- package/src/clever-client/k8s.js +96 -0
- package/src/commands/addon.js +119 -146
- package/src/commands/diag.js +33 -4
- package/src/commands/k8s.js +193 -0
- package/src/commands/ng.js +1 -1
- package/src/experimental-features.js +32 -5
- package/src/lib/ascii.js +48 -0
- package/src/lib/k8s.js +126 -0
- package/src/lib/prompts.js +2 -0
- package/src/models/ids-resolver.js +11 -0
- package/src/models/log-v4.js +1 -1
- package/src/models/ng-resources.js +16 -28
- package/src/models/ng.js +19 -30
- package/src/parsers.js +5 -3
package/bin/clever.js
CHANGED
|
@@ -28,6 +28,7 @@ import * as drain from '../src/commands/drain.js';
|
|
|
28
28
|
import * as emails from '../src/commands/emails.js';
|
|
29
29
|
import * as env from '../src/commands/env.js';
|
|
30
30
|
import * as features from '../src/commands/features.js';
|
|
31
|
+
import * as k8s from '../src/commands/k8s.js';
|
|
31
32
|
import * as keycloak from '../src/commands/keycloak.js';
|
|
32
33
|
import * as kv from '../src/commands/kv.js';
|
|
33
34
|
import * as link from '../src/commands/link.js';
|
|
@@ -79,6 +80,10 @@ process.stdout.on('error', (error) => {
|
|
|
79
80
|
const cliparseCommand = cliparse.command;
|
|
80
81
|
|
|
81
82
|
cliparse.command = function (name, options, commandFunction) {
|
|
83
|
+
if (commandFunction == null) {
|
|
84
|
+
return cliparseCommand(name, options);
|
|
85
|
+
}
|
|
86
|
+
|
|
82
87
|
return cliparseCommand(name, options, (...args) => {
|
|
83
88
|
const promise = commandFunction(...args);
|
|
84
89
|
handleCommandPromise(promise);
|
|
@@ -95,6 +100,11 @@ function colorizeExperimentalCommand(command, id) {
|
|
|
95
100
|
async function run() {
|
|
96
101
|
// ARGUMENTS
|
|
97
102
|
const args = {
|
|
103
|
+
k8sClusterName: cliparse.argument('cluster-name', { description: 'Kubernetes cluster name' }),
|
|
104
|
+
k8sIdOrName: cliparse.argument('id-or-name', {
|
|
105
|
+
description: 'Kubernetes cluster ID or name',
|
|
106
|
+
parser: Parsers.addonIdOrName,
|
|
107
|
+
}),
|
|
98
108
|
kvRawCommand: cliparse.argument('command', {
|
|
99
109
|
description: 'The raw command to send to the Materia KV or Redis® add-on',
|
|
100
110
|
}),
|
|
@@ -120,12 +130,16 @@ async function run() {
|
|
|
120
130
|
parser: Parsers.ngResourceType,
|
|
121
131
|
}),
|
|
122
132
|
ngAnyIdOrLabel: cliparse.argument('id-or-label', {
|
|
123
|
-
description: 'ID or Label of a Network
|
|
133
|
+
description: 'ID or Label of a Network Group, a member or an (external) peer',
|
|
134
|
+
parser: Parsers.ngResourceType,
|
|
135
|
+
}),
|
|
136
|
+
ngResourceId: cliparse.argument('id', {
|
|
137
|
+
description: 'ID of a resource to (un)link to a Network Group',
|
|
124
138
|
parser: Parsers.ngResourceType,
|
|
125
139
|
}),
|
|
126
140
|
wgPublicKey: cliparse.argument('public-key', {
|
|
127
141
|
metavar: 'public_key',
|
|
128
|
-
description: '
|
|
142
|
+
description: 'WireGuard public key of the external peer to link to a Network Group',
|
|
129
143
|
}),
|
|
130
144
|
email: cliparse.argument('email', {
|
|
131
145
|
description: 'Email address',
|
|
@@ -188,6 +202,13 @@ async function run() {
|
|
|
188
202
|
|
|
189
203
|
// OPTIONS
|
|
190
204
|
const opts = {
|
|
205
|
+
k8sDeployWatch: cliparse.flag('watch', {
|
|
206
|
+
aliases: ['w'],
|
|
207
|
+
description: 'Watch the deployment until the cluster is deployed',
|
|
208
|
+
}),
|
|
209
|
+
kubeConfigForceDownload: cliparse.flag('force', {
|
|
210
|
+
description: 'Force the download of the kubeconfig file, even if it already exists',
|
|
211
|
+
}),
|
|
191
212
|
targetVersion: cliparse.option('target', {
|
|
192
213
|
metavar: 'version',
|
|
193
214
|
description: 'Target version to upgrade to (e.g.: 24, 2.4, 2.4.1)',
|
|
@@ -206,7 +227,7 @@ async function run() {
|
|
|
206
227
|
ngMembersIdsToLink: cliparse.option('link', {
|
|
207
228
|
metavar: 'members_ids',
|
|
208
229
|
description:
|
|
209
|
-
|
|
230
|
+
'Comma separated list of members IDs to link to a Network Group (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.)',
|
|
210
231
|
parser: Parsers.commaSeparated,
|
|
211
232
|
}),
|
|
212
233
|
ngResourceType: cliparse.option('type', {
|
|
@@ -1035,6 +1056,73 @@ async function run() {
|
|
|
1035
1056
|
features.list,
|
|
1036
1057
|
);
|
|
1037
1058
|
|
|
1059
|
+
// K8S COMMAND
|
|
1060
|
+
const k8sListCommand = cliparse.command(
|
|
1061
|
+
'list',
|
|
1062
|
+
{
|
|
1063
|
+
description: 'List Kubernetes clusters',
|
|
1064
|
+
options: [opts.orgaIdOrName, opts.humanJsonOutputFormat],
|
|
1065
|
+
},
|
|
1066
|
+
k8s.list,
|
|
1067
|
+
);
|
|
1068
|
+
const k8sCreateCommand = cliparse.command(
|
|
1069
|
+
'create',
|
|
1070
|
+
{
|
|
1071
|
+
description: 'Create a Kubernetes cluster',
|
|
1072
|
+
args: [args.k8sClusterName],
|
|
1073
|
+
options: [opts.orgaIdOrName, opts.k8sDeployWatch],
|
|
1074
|
+
},
|
|
1075
|
+
k8s.create,
|
|
1076
|
+
);
|
|
1077
|
+
const k8sDeleteCommand = cliparse.command(
|
|
1078
|
+
'delete',
|
|
1079
|
+
{
|
|
1080
|
+
description: 'Delete a Kubernetes cluster',
|
|
1081
|
+
options: [opts.orgaIdOrName, opts.confirmAddonDeletion],
|
|
1082
|
+
args: [args.k8sIdOrName],
|
|
1083
|
+
},
|
|
1084
|
+
k8s.del,
|
|
1085
|
+
);
|
|
1086
|
+
const k8sGetCommand = cliparse.command(
|
|
1087
|
+
'get',
|
|
1088
|
+
{
|
|
1089
|
+
description: 'Get information about a Kubernetes cluster',
|
|
1090
|
+
args: [args.k8sIdOrName],
|
|
1091
|
+
options: [opts.orgaIdOrName, opts.humanJsonOutputFormat],
|
|
1092
|
+
},
|
|
1093
|
+
k8s.get,
|
|
1094
|
+
);
|
|
1095
|
+
const k8sGetConfigCommand = cliparse.command(
|
|
1096
|
+
'get-kubeconfig',
|
|
1097
|
+
{
|
|
1098
|
+
description: 'Get configuration of a Kubernetes cluster',
|
|
1099
|
+
args: [args.k8sIdOrName],
|
|
1100
|
+
options: [opts.orgaIdOrName],
|
|
1101
|
+
},
|
|
1102
|
+
k8s.getConfig,
|
|
1103
|
+
);
|
|
1104
|
+
const k8sAddPersistentStorageCommand = cliparse.command(
|
|
1105
|
+
'add-persistent-storage',
|
|
1106
|
+
{
|
|
1107
|
+
description: 'Activate persistent storage to a deployed Kubernetes cluster',
|
|
1108
|
+
args: [args.k8sIdOrName],
|
|
1109
|
+
options: [opts.orgaIdOrName],
|
|
1110
|
+
},
|
|
1111
|
+
k8s.addPersistentStorage,
|
|
1112
|
+
);
|
|
1113
|
+
|
|
1114
|
+
const k8sCommand = cliparse.command('k8s', {
|
|
1115
|
+
description: 'Manage Kubernetes clusters',
|
|
1116
|
+
commands: [
|
|
1117
|
+
k8sListCommand,
|
|
1118
|
+
k8sCreateCommand,
|
|
1119
|
+
k8sAddPersistentStorageCommand,
|
|
1120
|
+
k8sDeleteCommand,
|
|
1121
|
+
k8sGetCommand,
|
|
1122
|
+
k8sGetConfigCommand,
|
|
1123
|
+
],
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1038
1126
|
// KEYCLOAK COMMAND
|
|
1039
1127
|
const keycloakGetCommand = cliparse.command(
|
|
1040
1128
|
'get',
|
|
@@ -1413,16 +1501,18 @@ async function run() {
|
|
|
1413
1501
|
const ngLinkCommand = cliparse.command(
|
|
1414
1502
|
'link',
|
|
1415
1503
|
{
|
|
1416
|
-
description:
|
|
1417
|
-
|
|
1504
|
+
description:
|
|
1505
|
+
'Link a resource by its ID (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.) to a Network Group',
|
|
1506
|
+
args: [args.ngResourceId, args.ngIdOrLabel],
|
|
1418
1507
|
},
|
|
1419
1508
|
ng.linkToNg,
|
|
1420
1509
|
);
|
|
1421
1510
|
const ngUnlinkCommand = cliparse.command(
|
|
1422
1511
|
'unlink',
|
|
1423
1512
|
{
|
|
1424
|
-
description:
|
|
1425
|
-
|
|
1513
|
+
description:
|
|
1514
|
+
'Unlink a resource by its ID (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.) from a Network Group',
|
|
1515
|
+
args: [args.ngResourceId, args.ngIdOrLabel],
|
|
1426
1516
|
},
|
|
1427
1517
|
ng.unlinkFromNg,
|
|
1428
1518
|
);
|
|
@@ -1438,7 +1528,7 @@ async function run() {
|
|
|
1438
1528
|
const ngGetConfigCommand = cliparse.command(
|
|
1439
1529
|
'get-config',
|
|
1440
1530
|
{
|
|
1441
|
-
description: 'Get the
|
|
1531
|
+
description: 'Get the WireGuard configuration of a peer in a Network Group',
|
|
1442
1532
|
args: [args.ngExternalIdOrLabel, args.ngIdOrLabel],
|
|
1443
1533
|
options: [opts.humanJsonOutputFormat],
|
|
1444
1534
|
},
|
|
@@ -1977,18 +2067,10 @@ async function run() {
|
|
|
1977
2067
|
},
|
|
1978
2068
|
database.listBackups,
|
|
1979
2069
|
);
|
|
1980
|
-
const databaseCommand = cliparse.command(
|
|
1981
|
-
'
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
commands: [backupsCommand],
|
|
1985
|
-
},
|
|
1986
|
-
async () => {
|
|
1987
|
-
console.info('This command is not available, you can try the following commands:');
|
|
1988
|
-
console.info('clever database backups');
|
|
1989
|
-
console.info('clever database backups download');
|
|
1990
|
-
},
|
|
1991
|
-
);
|
|
2070
|
+
const databaseCommand = cliparse.command('database', {
|
|
2071
|
+
description: 'Manage databases and backups',
|
|
2072
|
+
commands: [backupsCommand],
|
|
2073
|
+
});
|
|
1992
2074
|
|
|
1993
2075
|
// Patch help command description
|
|
1994
2076
|
cliparseCommands.helpCommand.description = 'Display help about the Clever Cloud CLI';
|
|
@@ -2046,6 +2128,10 @@ async function run() {
|
|
|
2046
2128
|
commands.push(colorizeExperimentalCommand(otoroshiCommand, 'operators'));
|
|
2047
2129
|
}
|
|
2048
2130
|
|
|
2131
|
+
if (featuresFromConf.k8s) {
|
|
2132
|
+
commands.push(colorizeExperimentalCommand(k8sCommand, 'k8s'));
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2049
2135
|
if (featuresFromConf.kv) {
|
|
2050
2136
|
commands.push(colorizeExperimentalCommand(kvRawCommand, 'kv'));
|
|
2051
2137
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// TODO: Move this to the Clever Cloud JS Client
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* POST /v4/kubernetes/organisations/{ownerId}/clusters
|
|
5
|
+
* @param {Object} params
|
|
6
|
+
* @param {String} params.ownerId
|
|
7
|
+
* @param {Object} body
|
|
8
|
+
*/
|
|
9
|
+
export function createK8sCluster(params, body) {
|
|
10
|
+
return Promise.resolve({
|
|
11
|
+
method: 'post',
|
|
12
|
+
url: `/v4/kubernetes/organisations/${params.ownerId}/clusters`,
|
|
13
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
14
|
+
// no queryParams
|
|
15
|
+
body,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* DELETE /v4/kubernetes/organisations/{ownerId}/clusters/{clusterId}
|
|
21
|
+
* @param {Object} params
|
|
22
|
+
* @param {String} params.ownerId
|
|
23
|
+
* @param {String} params.clusterId
|
|
24
|
+
*/
|
|
25
|
+
export function deleteK8sCluster(params) {
|
|
26
|
+
return Promise.resolve({
|
|
27
|
+
method: 'delete',
|
|
28
|
+
url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}`,
|
|
29
|
+
headers: { Accept: 'application/json' },
|
|
30
|
+
// no queryParams
|
|
31
|
+
// no body
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* GET /v4/kubernetes/organisations/{ownerId}/clusters
|
|
37
|
+
* @param {Object} params
|
|
38
|
+
* @param {String} params.ownerId
|
|
39
|
+
*/
|
|
40
|
+
export function listK8sClusters(params) {
|
|
41
|
+
return Promise.resolve({
|
|
42
|
+
method: 'get',
|
|
43
|
+
url: `/v4/kubernetes/organisations/${params.ownerId}/clusters`,
|
|
44
|
+
headers: { Accept: 'application/json' },
|
|
45
|
+
// no queryParams
|
|
46
|
+
// no body
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* GET /v4/kubernetes/organisations/{ownerId}/clusters/{clusterId}
|
|
52
|
+
* @param {Object} params
|
|
53
|
+
* @param {String} params.ownerId
|
|
54
|
+
* @param {String} params.clusterId
|
|
55
|
+
*/
|
|
56
|
+
export function getK8sAddon(params) {
|
|
57
|
+
return Promise.resolve({
|
|
58
|
+
method: 'get',
|
|
59
|
+
url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}`,
|
|
60
|
+
headers: { Accept: 'application/json' },
|
|
61
|
+
// no queryParams
|
|
62
|
+
// no body
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* GET /v4/kubernetes/organisations/{ownerId}/clusters/{clusterId}/kubeconfig.yaml
|
|
68
|
+
* @param {Object} params
|
|
69
|
+
* @param {String} params.ownerId
|
|
70
|
+
* @param {String} params.clusterId
|
|
71
|
+
*/
|
|
72
|
+
export function getK8sConfig(params) {
|
|
73
|
+
return Promise.resolve({
|
|
74
|
+
method: 'get',
|
|
75
|
+
url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}/kubeconfig.yaml`,
|
|
76
|
+
// headers: { Accept: 'application/x-yaml' },
|
|
77
|
+
// no queryParams
|
|
78
|
+
// no body
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* POST /v4/kubernetes/organisations/ownerId}/clusters/{clusterId}/csi/ceph
|
|
84
|
+
* @param {Object} params
|
|
85
|
+
* @param {String} params.ownerId
|
|
86
|
+
* @param {String} params.clusterId
|
|
87
|
+
*/
|
|
88
|
+
export function addK8sPersistentStorage(params) {
|
|
89
|
+
return Promise.resolve({
|
|
90
|
+
method: 'post',
|
|
91
|
+
url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}/csi/ceph`,
|
|
92
|
+
headers: { Accept: 'application/json' },
|
|
93
|
+
// no queryParams
|
|
94
|
+
// no body
|
|
95
|
+
});
|
|
96
|
+
}
|
package/src/commands/addon.js
CHANGED
|
@@ -3,6 +3,7 @@ import { styleText } from '../lib/style-text.js';
|
|
|
3
3
|
import { getAllEnvVars } from '@clevercloud/client/esm/api/v2/addon.js';
|
|
4
4
|
import { toNameEqualsValueString } from '@clevercloud/client/esm/utils/env-vars.js';
|
|
5
5
|
import dedent from 'dedent';
|
|
6
|
+
import { getOperator } from '../clever-client/operators.js';
|
|
6
7
|
import { formatTable } from '../format-table.js';
|
|
7
8
|
import { Logger } from '../logger.js';
|
|
8
9
|
import * as Addon from '../models/addon.js';
|
|
@@ -53,6 +54,45 @@ export async function list(params) {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
const ADDON_PROVIDERS = {
|
|
58
|
+
keycloak: {
|
|
59
|
+
name: 'Keycloak',
|
|
60
|
+
isOperator: true,
|
|
61
|
+
postCreateInstructions: `Learn more about Keycloak on Clever Cloud: ${conf.DOC_URL}/addons/keycloak/`,
|
|
62
|
+
},
|
|
63
|
+
kv: {
|
|
64
|
+
name: 'Materia KV',
|
|
65
|
+
isOperator: false,
|
|
66
|
+
status: 'beta',
|
|
67
|
+
postCreateInstructions: (addonId) => dedent`
|
|
68
|
+
${styleText('yellow', "You can easily use Materia KV with 'redis-cli', with such commands:")}
|
|
69
|
+
${styleText('blue', `source <(clever addon env ${addonId} -F shell)`)}
|
|
70
|
+
${styleText('blue', 'redis-cli -h $KV_HOST -p $KV_PORT --tls')}
|
|
71
|
+
Learn more about Materia KV on Clever Cloud: ${conf.DOC_URL}/addons/materia-kv/
|
|
72
|
+
`,
|
|
73
|
+
},
|
|
74
|
+
'addon-matomo': {
|
|
75
|
+
name: 'Matomo',
|
|
76
|
+
isOperator: true,
|
|
77
|
+
postCreateInstructions: `Learn more about Matomo on Clever Cloud: ${conf.DOC_URL}/addons/matomo/`,
|
|
78
|
+
},
|
|
79
|
+
metabase: {
|
|
80
|
+
name: 'Metabase',
|
|
81
|
+
isOperator: true,
|
|
82
|
+
postCreateInstructions: `Learn more about Metabase on Clever Cloud: ${conf.DOC_URL}/addons/metabase/`,
|
|
83
|
+
},
|
|
84
|
+
otoroshi: {
|
|
85
|
+
name: 'Otoroshi with LLM',
|
|
86
|
+
isOperator: true,
|
|
87
|
+
postCreateInstructions: `Learn more about Otoroshi with LLM on Clever Cloud: ${conf.DOC_URL}/addons/otoroshi/`,
|
|
88
|
+
},
|
|
89
|
+
'addon-pulsar': {
|
|
90
|
+
name: 'Pulsar',
|
|
91
|
+
isOperator: false,
|
|
92
|
+
postCreateInstructions: `Learn more about Pulsar on Clever Cloud: ${conf.DOC_URL}/addons/pulsar/`,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
56
96
|
export async function create(params) {
|
|
57
97
|
const [providerName, name] = params.args;
|
|
58
98
|
const {
|
|
@@ -62,23 +102,21 @@ export async function create(params) {
|
|
|
62
102
|
yes: skipConfirmation,
|
|
63
103
|
org: orgaIdOrName,
|
|
64
104
|
format,
|
|
105
|
+
'addon-version': version,
|
|
106
|
+
option: addonOptions,
|
|
65
107
|
} = params.options;
|
|
66
|
-
const version = params.options['addon-version'];
|
|
67
|
-
const addonOptions = parseAddonOptions(params.options.option);
|
|
68
|
-
|
|
69
|
-
const ownerId = orgaIdOrName != null ? await Organisation.getId(orgaIdOrName) : await User.getCurrentId();
|
|
70
108
|
|
|
71
109
|
const addonToCreate = {
|
|
72
|
-
ownerId,
|
|
73
110
|
name,
|
|
74
111
|
providerName,
|
|
75
112
|
planName,
|
|
76
113
|
region,
|
|
77
114
|
skipConfirmation,
|
|
78
115
|
version,
|
|
79
|
-
addonOptions,
|
|
116
|
+
addonOptions: parseAddonOptions(addonOptions),
|
|
80
117
|
};
|
|
81
118
|
|
|
119
|
+
const ownerId = orgaIdOrName != null ? await Organisation.getId(orgaIdOrName) : await User.getCurrentId();
|
|
82
120
|
if (linkedAppAlias != null) {
|
|
83
121
|
const linkedAppData = await AppConfig.getAppDetails({ alias: linkedAppAlias });
|
|
84
122
|
if (orgaIdOrName != null && linkedAppData.ownerId !== ownerId && format === 'human') {
|
|
@@ -86,163 +124,98 @@ export async function create(params) {
|
|
|
86
124
|
'The specified application does not belong to the specified organisation. Ignoring the `--org` option',
|
|
87
125
|
);
|
|
88
126
|
}
|
|
89
|
-
|
|
90
|
-
...addonToCreate,
|
|
91
|
-
ownerId: linkedAppData.ownerId,
|
|
92
|
-
});
|
|
93
|
-
await Addon.link(linkedAppData.ownerId, linkedAppData.appId, { addon_id: newAddon.id });
|
|
94
|
-
displayAddon(
|
|
95
|
-
format,
|
|
96
|
-
newAddon,
|
|
97
|
-
providerName,
|
|
98
|
-
`Add-on created and linked to application ${linkedAppAlias} successfully!`,
|
|
99
|
-
);
|
|
127
|
+
addonToCreate.ownerId = linkedAppData.ownerId;
|
|
100
128
|
} else {
|
|
101
|
-
|
|
102
|
-
displayAddon(format, newAddon, providerName, 'Add-on created successfully!');
|
|
129
|
+
addonToCreate.ownerId = ownerId;
|
|
103
130
|
}
|
|
104
|
-
}
|
|
105
131
|
|
|
106
|
-
|
|
107
|
-
const PROVIDERS_WITH_URL = {
|
|
108
|
-
keycloak: {
|
|
109
|
-
name: 'Keycloak',
|
|
110
|
-
urlEnv: 'CC_KEYCLOAK_URL',
|
|
111
|
-
},
|
|
112
|
-
'addon-matomo': {
|
|
113
|
-
name: 'Matomo',
|
|
114
|
-
urlEnv: 'MATOMO_URL',
|
|
115
|
-
},
|
|
116
|
-
metabase: {
|
|
117
|
-
name: 'Metabase',
|
|
118
|
-
urlEnv: 'METABASE_URL',
|
|
119
|
-
},
|
|
120
|
-
otoroshi: {
|
|
121
|
-
name: 'Otoroshi with LLM',
|
|
122
|
-
urlEnv: 'CC_OTOROSHI_URL',
|
|
123
|
-
},
|
|
124
|
-
};
|
|
132
|
+
const newAddon = await Addon.create(addonToCreate);
|
|
125
133
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Learn more about Keycloak on Clever Cloud: ${conf.DOC_URL}/addons/keycloak/,
|
|
131
|
-
`,
|
|
132
|
-
},
|
|
133
|
-
kv: {
|
|
134
|
-
status: 'alpha',
|
|
135
|
-
postCreateInstructions: dedent`
|
|
136
|
-
${styleText('yellow', "You can easily use Materia KV with 'redis-cli', with such commands:")}
|
|
137
|
-
${styleText('blue', `source <(clever addon env ${addon.id} -F shell)`)}
|
|
138
|
-
${styleText('blue', 'redis-cli -h $KV_HOST -p $KV_PORT --tls')}
|
|
139
|
-
Learn more about Materia KV on Clever Cloud: ${conf.DOC_URL}/addons/materia-kv/
|
|
140
|
-
`,
|
|
141
|
-
},
|
|
142
|
-
'addon-matomo': {
|
|
143
|
-
status: '',
|
|
144
|
-
postCreateInstructions: dedent`
|
|
145
|
-
Learn more about Matomo on Clever Cloud: ${conf.DOC_URL}/addons/matomo/
|
|
146
|
-
`,
|
|
147
|
-
},
|
|
148
|
-
metabase: {
|
|
149
|
-
status: '',
|
|
150
|
-
postCreateInstructions: dedent`
|
|
151
|
-
Learn more about Metabase on Clever Cloud: ${conf.DOC_URL}/addons/metabase/
|
|
152
|
-
`,
|
|
153
|
-
},
|
|
154
|
-
otoroshi: {
|
|
155
|
-
status: '',
|
|
156
|
-
postCreateInstructions: dedent`
|
|
157
|
-
Learn more about Otoroshi with LLM on Clever Cloud: ${conf.DOC_URL}/addons/otoroshi/
|
|
158
|
-
`,
|
|
159
|
-
},
|
|
160
|
-
'addon-pulsar': {
|
|
161
|
-
status: 'beta',
|
|
162
|
-
postCreateInstructions: dedent`
|
|
163
|
-
Learn more about Pulsar on Clever Cloud: ${conf.DOC_URL}/addons/pulsar/
|
|
164
|
-
`,
|
|
165
|
-
},
|
|
166
|
-
};
|
|
134
|
+
if (linkedAppAlias != null) {
|
|
135
|
+
const linkedAppData = await AppConfig.getAppDetails({ alias: linkedAppAlias });
|
|
136
|
+
await Addon.link(linkedAppData.ownerId, linkedAppData.appId, { addon_id: newAddon.id });
|
|
137
|
+
}
|
|
167
138
|
|
|
168
|
-
|
|
139
|
+
const provider = ADDON_PROVIDERS[providerName];
|
|
169
140
|
let statusMessage = '';
|
|
170
|
-
if (
|
|
171
|
-
|
|
141
|
+
if (provider?.status) {
|
|
142
|
+
statusMessage = `The ${provider.name} provider is in ${provider.status} testing phase`;
|
|
143
|
+
if (provider.status === 'alpha') {
|
|
144
|
+
statusMessage += ". Don't store sensitive or production grade data.";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// JSON format
|
|
149
|
+
if (format === 'json') {
|
|
150
|
+
const jsonAddon = {
|
|
151
|
+
id: newAddon.id,
|
|
152
|
+
realId: newAddon.realId,
|
|
153
|
+
name: newAddon.name,
|
|
154
|
+
env: newAddon.env,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (provider?.status) {
|
|
158
|
+
jsonAddon.availability = provider.status;
|
|
159
|
+
jsonAddon.message = statusMessage;
|
|
160
|
+
}
|
|
172
161
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
WIP_PROVIDERS[providerName].status === 'alpha' ? ". Don't store sensitive or production grade data." : '';
|
|
162
|
+
Logger.printJson(jsonAddon);
|
|
163
|
+
return;
|
|
176
164
|
}
|
|
177
165
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
env: addon.env,
|
|
185
|
-
};
|
|
166
|
+
// Human format
|
|
167
|
+
if (linkedAppAlias != null) {
|
|
168
|
+
Logger.println(`Add-on created and linked to application ${linkedAppAlias} successfully!`);
|
|
169
|
+
} else {
|
|
170
|
+
Logger.println('Add-on created successfully!');
|
|
171
|
+
}
|
|
186
172
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
: jsonAddon,
|
|
191
|
-
);
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
173
|
+
Logger.println(`ID: ${newAddon.id}`);
|
|
174
|
+
Logger.println(`Real ID: ${newAddon.realId}`);
|
|
175
|
+
Logger.println(`Name: ${newAddon.name}`);
|
|
194
176
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (providerName in PROVIDERS_WITH_URL) {
|
|
200
|
-
const provider = PROVIDERS_WITH_URL[providerName];
|
|
201
|
-
const urlEnv = addon.env.find((entry) => entry.name === provider.urlEnv);
|
|
202
|
-
const urlToShow = urlEnv.value;
|
|
203
|
-
const urlToShowWithHttps = urlToShow.startsWith('http') ? urlToShow : `https://${urlToShow}`;
|
|
204
|
-
|
|
205
|
-
if (urlEnv) {
|
|
206
|
-
Logger.println();
|
|
207
|
-
Logger.println(`Your ${provider.name} is starting:`);
|
|
208
|
-
Logger.println(` - Access it: ${urlToShowWithHttps}`);
|
|
209
|
-
Logger.println(` - Manage it: ${conf.GOTO_URL}/${addon.id}`);
|
|
210
|
-
}
|
|
177
|
+
const operator = ADDON_PROVIDERS[providerName]?.isOperator
|
|
178
|
+
? await getOperator({ provider: providerName, realId: newAddon.realId }).then(sendToApi)
|
|
179
|
+
: null;
|
|
211
180
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
` - Temporary password: ${addon.env.find((e) => e.name === 'CC_KEYCLOAK_ADMIN_DEFAULT_PASSWORD').value}`,
|
|
220
|
-
);
|
|
221
|
-
}
|
|
181
|
+
if (operator) {
|
|
182
|
+
if (operator.accessUrl) {
|
|
183
|
+
Logger.println();
|
|
184
|
+
Logger.println(`Your ${ADDON_PROVIDERS[providerName].name} is starting:`);
|
|
185
|
+
Logger.println(` - Access it: ${operator.accessUrl}`);
|
|
186
|
+
Logger.println(` - Manage it: ${conf.GOTO_URL}/${newAddon.id}`);
|
|
187
|
+
}
|
|
222
188
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
` - Admin user name: ${addon.env.find((e) => e.name === 'CC_OTOROSHI_INITIAL_ADMIN_LOGIN').value}`,
|
|
230
|
-
);
|
|
231
|
-
Logger.println(
|
|
232
|
-
` - Initial password: ${addon.env.find((e) => e.name === 'CC_OTOROSHI_INITIAL_ADMIN_PASSWORD').value}`,
|
|
233
|
-
);
|
|
234
|
-
}
|
|
189
|
+
if (operator.initialCredentials) {
|
|
190
|
+
if (providerName === 'keycloak') {
|
|
191
|
+
Logger.println();
|
|
192
|
+
Logger.println("An initial account has been created, you'll be invited to change the password at first login:");
|
|
193
|
+
Logger.println(` - Admin user name: ${operator.initialCredentials.user}`);
|
|
194
|
+
Logger.println(` - Temporary password: ${operator.initialCredentials.password}`);
|
|
235
195
|
}
|
|
236
|
-
|
|
237
|
-
if (providerName in WIP_PROVIDERS) {
|
|
196
|
+
if (providerName === 'otoroshi') {
|
|
238
197
|
Logger.println();
|
|
198
|
+
Logger.println(
|
|
199
|
+
'An initial account has been created, change the password at first login (Security -> Administrators -> Edit user):',
|
|
200
|
+
);
|
|
201
|
+
Logger.println(` - Admin user name: ${operator.initialCredentials.user}`);
|
|
202
|
+
Logger.println(` - Password: ${operator.initialCredentials.password}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
239
206
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
207
|
+
if (provider?.postCreateInstructions) {
|
|
208
|
+
Logger.println();
|
|
243
209
|
|
|
244
|
-
|
|
245
|
-
}
|
|
210
|
+
if (statusMessage !== '') {
|
|
211
|
+
Logger.println(styleText('yellow', `/!\\ ${statusMessage}`));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (typeof provider.postCreateInstructions === 'function') {
|
|
215
|
+
Logger.println(provider.postCreateInstructions(newAddon.id));
|
|
216
|
+
} else {
|
|
217
|
+
Logger.println(provider.postCreateInstructions);
|
|
218
|
+
}
|
|
246
219
|
}
|
|
247
220
|
}
|
|
248
221
|
|