Skip to content

Commit d419b9a

Browse files
davlgdhsablonniere
andcommitted
feat: add functions command
Co-Authored-By: Hubert SABLONNIÈRE <[email protected]>
1 parent c590d83 commit d419b9a

File tree

2 files changed

+245
-0
lines changed

2 files changed

+245
-0
lines changed

bin/clever.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import * as domain from '../src/commands/domain.js';
3838
import * as drain from '../src/commands/drain.js';
3939
import * as env from '../src/commands/env.js';
4040
import * as features from '../src/commands/features.js';
41+
import * as functions from '../src/commands/functions.js';
4142
import * as kv from '../src/commands/kv.js';
4243
import * as link from '../src/commands/link.js';
4344
import * as login from '../src/commands/login.js';
@@ -91,6 +92,12 @@ async function run () {
9192

9293
// ARGUMENTS
9394
const args = {
95+
faasId: cliparse.argument('faas-id', {
96+
description: 'Function ID',
97+
}),
98+
faasFile: cliparse.argument('filename', {
99+
description: 'Path to the function code',
100+
}),
94101
kvRawCommand: cliparse.argument('command', {
95102
description: 'The raw command to send to the Materia KV or Redis® add-on',
96103
}),
@@ -767,6 +774,29 @@ async function run () {
767774
commands: [enableFeatureCommand, disableFeatureCommand, listFeaturesCommand, infoFeaturesCommand],
768775
}, features.list);
769776

777+
// FUNCTIONS COMMANDS
778+
const functionsCreateCommand = cliparse.command('create', {
779+
description: 'Create a Clever Cloud Function',
780+
}, functions.create);
781+
const functionsDeleteCommand = cliparse.command('delete', {
782+
description: 'Delete a Clever Cloud Function',
783+
args: [args.faasId],
784+
}, functions.destroy);
785+
const functionsDeployCommand = cliparse.command('deploy', {
786+
description: 'Deploy a Clever Cloud Function from compatible source code',
787+
args: [args.faasFile, args.faasId],
788+
}, functions.deploy);
789+
const functionsListDeploymentsCommand = cliparse.command('list-deployments', {
790+
description: 'List deployments of a Clever Cloud Function',
791+
args: [args.faasId],
792+
options: [opts.humanJsonOutputFormat],
793+
}, functions.listDeployments);
794+
const functionsCommand = cliparse.command('functions', {
795+
description: 'Manage Clever Cloud Functions',
796+
options: [opts.orgaIdOrName],
797+
commands: [functionsCreateCommand, functionsDeleteCommand, functionsDeployCommand, functionsListDeploymentsCommand],
798+
}, functions.list);
799+
770800
// KV COMMAND
771801
const kvRawCommand = cliparse.command('kv', {
772802
description: 'Send a raw command to a Materia KV or Redis® add-on',

src/commands/functions.js

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import fs from 'node:fs';
2+
import colors from 'colors/safe.js';
3+
4+
import * as User from '../models/user.js';
5+
import * as Organisation from '../models/organisation.js';
6+
7+
import { Logger } from '../logger.js';
8+
import { setTimeout } from 'timers/promises';
9+
import { sendToApi } from '../models/send-to-api.js';
10+
import { uploadFunction } from '../models/functions.js';
11+
import { createFunction, createDeployment, getDeployments, getDeployment, getFunctions, deleteDeployment, triggerDeployment, deleteFunction } from '../models/functions-api.js';
12+
13+
const DEFAULT_MAX_INSTANCES = 1;
14+
const DEFAULT_MAX_MEMORY = 64 * 1024 * 1024;
15+
16+
/**
17+
* Creates a new function
18+
* @param {Object} params
19+
* @param {Object} params.options
20+
* @param {Object} params.options.org - The organisation to create the function in
21+
* @returns {Promise<void>}
22+
* */
23+
export async function create (params) {
24+
const { org } = params.options;
25+
26+
const ownerId = (org != null && org.orga_name !== '')
27+
? await Organisation.getId(org)
28+
: (await User.getCurrent()).id;
29+
30+
const createdFunction = await createFunction({ ownerId }, {
31+
name: null,
32+
description: null,
33+
environment: {},
34+
tag: null,
35+
maxInstances: DEFAULT_MAX_INSTANCES,
36+
maxMemory: DEFAULT_MAX_MEMORY,
37+
}).then(sendToApi);
38+
39+
Logger.println(`${colors.green('✓')} Function ${colors.green(createdFunction.id)} successfully created!`);
40+
}
41+
42+
/**
43+
* Deploys a function
44+
* @param {Object} params
45+
* @param {Object} params.args
46+
* @param {string} params.args[0] - The file to deploy
47+
* @param {string} params.args[1] - The function ID to deploy to
48+
* @param {Object} params.options
49+
* @param {Object} params.options.org - The organisation to deploy the function to
50+
* @returns {Promise<void>}
51+
* @throws {Error} - If the file to deploy does not exist
52+
* @throws {Error} - If the function to deploy to does not exist
53+
* */
54+
export async function deploy (params) {
55+
const [functionFile, functionId] = params.args;
56+
const { org } = params.options;
57+
58+
const ownerId = (org != null && org.orga_name !== '')
59+
? await Organisation.getId(org)
60+
: (await User.getCurrent()).id;
61+
62+
if (!fs.existsSync(functionFile)) {
63+
throw new Error(`File ${colors.red(functionFile)} does not exist, it can't be deployed`);
64+
}
65+
66+
const functions = await getFunctions({ ownerId }).then(sendToApi);
67+
const functionToDeploy = functions.find((f) => f.id === functionId);
68+
69+
if (!functionToDeploy) {
70+
throw new Error(`Function ${colors.red(functionId)} not found, it can't be deployed`);
71+
}
72+
73+
Logger.info(`Deploying ${functionFile}`);
74+
Logger.info(`Deploying to function ${functionId} of user ${ownerId}`);
75+
76+
let deployment = await createDeployment({
77+
ownerId,
78+
functionId,
79+
}, {
80+
name: null,
81+
description: null,
82+
tag: null,
83+
platform: 'JAVA_SCRIPT',
84+
}).then(sendToApi);
85+
86+
await uploadFunction(deployment.uploadUrl, functionFile);
87+
88+
await triggerDeployment({
89+
ownerId,
90+
functionId,
91+
deploymentId: deployment.id,
92+
}).then(sendToApi);
93+
94+
Logger.println(`${colors.green('✓')} Function compiled and uploaded successfully!`);
95+
96+
await setTimeout(1_000);
97+
while (deployment.status !== 'READY') {
98+
deployment = await getDeployment({
99+
ownerId,
100+
functionId,
101+
deploymentId: deployment.id,
102+
}).then(sendToApi);
103+
await setTimeout(1_000);
104+
}
105+
106+
Logger.println(`${colors.green('✓')} Your function is now deployed!`);
107+
Logger.println(` └─ Test it: ${colors.blue(`curl https://functions-staging-preview.cleverapps.io/${functionId}`)}`);
108+
}
109+
110+
/**
111+
* Destroys a function and its deployments
112+
* @param {Object} params
113+
* @param {Object} params.args
114+
* @param {string} params.args[0] - The function ID to destroy
115+
* @param {Object} params.options
116+
* @param {Object} params.options.org - The organisation to destroy the function from
117+
* @returns {Promise<void>}
118+
* @throws {Error} - If the function to destroy does not exist
119+
* */
120+
export async function destroy (params) {
121+
const [functionId] = params.args;
122+
const { org } = params.options;
123+
124+
const ownerId = (org != null && org.orga_name !== '')
125+
? await Organisation.getId(org)
126+
: (await User.getCurrent()).id;
127+
128+
const functions = await getFunctions({ ownerId }).then(sendToApi);
129+
const functionToDelete = functions.find((f) => f.id === functionId);
130+
131+
if (!functionToDelete) {
132+
throw new Error(`Function ${colors.red(functionId)} not found, it can't be deleted`);
133+
}
134+
135+
const deployments = await getDeployments({ ownerId, functionId }).then(sendToApi);
136+
137+
deployments.forEach(async (d) => {
138+
await deleteDeployment({ ownerId, functionId, deploymentId: d.id }).then(sendToApi);
139+
});
140+
141+
await deleteFunction({ ownerId, functionId }).then(sendToApi);
142+
Logger.println(`${colors.green('✓')} Function ${colors.green(functionId)} and its deployments successfully deleted!`);
143+
}
144+
145+
/**
146+
* Lists all the functions of the current user or the current organisation
147+
* @param {Object} params
148+
* @param {Object} params.options
149+
* @param {Object} params.options.org - The organisation to list the functions from
150+
* @param {string} params.options.format - The format to display the functions
151+
* @returns {Promise<void>}
152+
*/
153+
export async function list (params) {
154+
const { org, format } = params.options;
155+
156+
const ownerId = (org != null && org.orga_name !== '')
157+
? await Organisation.getId(org)
158+
: (await User.getCurrent()).id;
159+
160+
const functions = await getFunctions({
161+
ownerId: ownerId,
162+
}).then(sendToApi);
163+
164+
if (functions.length < 1) {
165+
Logger.println(`${colors.blue('🔎')} No functions found, create one with ${colors.blue('clever functions create')} command`);
166+
return;
167+
}
168+
169+
switch (format) {
170+
case 'json':
171+
console.log(JSON.stringify(functions, null, 2));
172+
break;
173+
case 'human':
174+
default:
175+
console.table(functions, ['id', 'createdAt', 'updatedAt']);
176+
}
177+
}
178+
179+
/**
180+
* Lists all the deployments of a function
181+
* @param {Object} params
182+
* @param {Object} params.args
183+
* @param {string} params.args[0] - The function ID to list the deployments from
184+
* @param {Object} params.options
185+
* @param {Object} params.options.org - The organisation to list the deployments from
186+
* @param {string} params.options.format - The format to display the deployments
187+
* @returns {Promise<void>}
188+
* */
189+
export async function listDeployments (params) {
190+
const [functionId] = params.args;
191+
const { org, format } = params.options;
192+
193+
const ownerId = (org != null && org.orga_name !== '')
194+
? await Organisation.getId(org)
195+
: (await User.getCurrent()).id;
196+
197+
const deploymentsList = await getDeployments({
198+
ownerId: ownerId, functionId,
199+
}).then(sendToApi);
200+
201+
if (deploymentsList.length < 1) {
202+
Logger.println(`${colors.blue('🔎')} No deployments found for this function`);
203+
return;
204+
}
205+
206+
switch (format) {
207+
case 'json':
208+
console.log(JSON.stringify(deploymentsList, null, 2));
209+
break;
210+
case 'human':
211+
default:
212+
console.table(deploymentsList, ['id', 'status', 'createdAt', 'updatedAt']);
213+
console.log(`▶️ You can call your function with ${colors.blue(`curl https://functions-staging-preview.cleverapps.io/${functionId}`)}`);
214+
}
215+
}

0 commit comments

Comments
 (0)