Skip to content

Commit

Permalink
Merge pull request #36 from dimitrov-d/master
Browse files Browse the repository at this point in the history
Cloud functions SDK module
  • Loading branch information
dimitrov-d authored Oct 29, 2024
2 parents e48173a + cff8d21 commit dc7cc39
Show file tree
Hide file tree
Showing 19 changed files with 704 additions and 94 deletions.
210 changes: 128 additions & 82 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@apillon/cli",
"description": "▶◀ Apillon CLI tools ▶◀",
"version": "1.3.0",
"version": "1.4.0",
"author": "Apillon",
"license": "MIT",
"main": "./dist/index.js",
Expand All @@ -12,7 +12,7 @@
"type": "git",
"url": "https://github.com/Apillon/sdk"
},
"homepage": "https://wiki.apillon.io",
"homepage": "https://wiki.apillon.io/build/6-apillon-cli.html",
"declaration": true,
"files": [
"dist/**/*"
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import config from './config';
import { createHostingCommands } from './modules/hosting/hosting.commands';
import { createNftsCommands } from './modules/nfts/nfts.commands';
import { createStorageCommands } from './modules/storage/storage.commands';
import { createCloudFunctionsCommands } from './modules/cloud-functions/cloud-functions.commands';

const cli = new Command('apillon').version(config.VERSION);
cli.addHelpText(
Expand Down Expand Up @@ -47,5 +48,6 @@ cli.showHelpAfterError('Run with --help for additional information!');
createStorageCommands(cli);
createHostingCommands(cli);
createNftsCommands(cli);
createCloudFunctionsCommands(cli);

cli.parse();
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Command } from 'commander';
import {
createCloudFunction,
listCloudFunctions,
getCloudFunction,
createCloudFunctionJob,
setCloudFunctionEnvironment,
listCloudFunctionJobs,
deleteCloudFunctionJob,
} from './cloud-functions.service';
import { addPaginationOptions } from '../../lib/options';

export function createCloudFunctionsCommands(cli: Command) {
const cloudFunctions = cli
.command('cloud-functions')
.description('Commands for managing cloud functions on Apillon platform');

// CLOUD FUNCTIONS
const listCloudFunctionsCommand = cloudFunctions
.command('list')
.description('List all cloud functions')
.action(async function () {
await listCloudFunctions(this.optsWithGlobals());
});
addPaginationOptions(listCloudFunctionsCommand);

cloudFunctions
.command('get')
.description('Get details of a specific cloud function')
.requiredOption('--uuid <cloud-function-uuid>', 'Cloud Function UUID')
.action(async function () {
await getCloudFunction(this.optsWithGlobals());
});

cloudFunctions
.command('create')
.description('Create a new cloud function')
.requiredOption('--name <name>', 'Name of the cloud function')
.option('--description <description>', 'Description of the cloud function')
.action(async function () {
await createCloudFunction(this.optsWithGlobals());
});

// JOBS
cloudFunctions
.command('create-job')
.description('Create a job for a cloud function from a script file')
.requiredOption('--uuid <cloud-function-uuid>', 'Cloud Function UUID')
.requiredOption('--name <job-name>', 'Name of the job')
.requiredOption('--script <path>', 'Path to the script file')
.action(async function () {
await createCloudFunctionJob(this.optsWithGlobals());
});

cloudFunctions
.command('set-environment')
.description('Set environment variables for a cloud function')
.requiredOption('--uuid <cloud-function-uuid>', 'Cloud Function UUID')
.requiredOption(
'--variables <variables>',
'Environment variables in key=value format, separated by commas',
)
.action(async function () {
await setCloudFunctionEnvironment(this.optsWithGlobals());
});

cloudFunctions
.command('list-jobs')
.description('List all jobs for a specific cloud function')
.requiredOption('--uuid <cloud-function-uuid>', 'Cloud Function UUID')
.action(async function () {
await listCloudFunctionJobs(this.optsWithGlobals());
});

cloudFunctions
.command('delete-job')
.description('Delete a job from a cloud function')
.requiredOption('-j, --job-uuid <job-uuid>', 'Job UUID to delete')
.action(async function () {
await deleteCloudFunctionJob(this.optsWithGlobals());
});
}
123 changes: 123 additions & 0 deletions packages/cli/src/modules/cloud-functions/cloud-functions.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import { CloudFunctions } from '@apillon/sdk';
import { readFileSync } from 'fs';
import { GlobalOptions } from '../../lib/types';
import { paginate } from '../../lib/options';
import { withErrorHandler } from '../../lib/utils';
import { Storage } from '@apillon/sdk';

export async function listCloudFunctions(optsWithGlobals: GlobalOptions) {
await withErrorHandler(async () => {
const data = await new CloudFunctions(optsWithGlobals).listCloudFunctions({
...paginate(optsWithGlobals),
});
console.log(data.items.map((d) => d.serialize()));
});
}

export async function getCloudFunction(optsWithGlobals: GlobalOptions) {
await withErrorHandler(async () => {
const cloudFunction = await new CloudFunctions(optsWithGlobals)
.cloudFunction(optsWithGlobals.uuid)
.get();

cloudFunction.jobs = cloudFunction.jobs.map((job) => job.serialize());
console.info(cloudFunction.serialize());
});
}

export async function createCloudFunction(optsWithGlobals: GlobalOptions) {
await withErrorHandler(async () => {
const cloudFunctions = new CloudFunctions(optsWithGlobals);
const data = await cloudFunctions.createCloudFunction({
name: optsWithGlobals.name,
description: optsWithGlobals.description,
});
if (data) {
console.log(data.serialize());
console.log('Cloud function created successfully!');
}
});
}

export async function createCloudFunctionJob(optsWithGlobals: GlobalOptions) {
await withErrorHandler(async () => {
let scriptContent: string;
const script = optsWithGlobals.script;

if (!script.endsWith('.js')) {
return console.error('The script file must have a .js extension.');
}

try {
scriptContent = readFileSync(script, 'utf-8');
} catch (e) {
return e.code === 'ENOENT'
? console.error(`Error: Script file not found (${script}).`)
: console.error(e);
}

const cloudFunction = await new CloudFunctions(optsWithGlobals)
.cloudFunction(optsWithGlobals.uuid)
.get();

// Upload the script to IPFS
const files = await new Storage(optsWithGlobals)
.bucket(cloudFunction.bucketUuid)
.uploadFiles(
[
{
fileName: script.split('/').pop(),
content: Buffer.from(scriptContent, 'utf-8'),
contentType: 'application/javascript',
},
],
{ awaitCid: true },
);

const job = await cloudFunction.createJob({
name: optsWithGlobals.name,
scriptCid: files[0].CID,
});

console.log(job.serialize());
console.log('Cloud function job created successfully!');
});
}

export async function setCloudFunctionEnvironment(
optsWithGlobals: GlobalOptions,
) {
await withErrorHandler(async () => {
const variables = optsWithGlobals.variables.split(',').map((v) => {
const [key, value] = v.split('=');
return { key, value };
});

await new CloudFunctions(optsWithGlobals)
.cloudFunction(optsWithGlobals.uuid)
.setEnvironment(variables);

console.log('Environment variables set successfully!');
});
}

export async function listCloudFunctionJobs(optsWithGlobals: GlobalOptions) {
await withErrorHandler(async () => {
const data = await new CloudFunctions(optsWithGlobals)
.cloudFunction(optsWithGlobals.uuid)
.get();

console.log(data.jobs.map((job) => job.serialize()));
});
}

export async function deleteCloudFunctionJob(optsWithGlobals: GlobalOptions) {
await withErrorHandler(async () => {
await new CloudFunctions(optsWithGlobals)
.cloudFunctionJob(optsWithGlobals.jobUuid)
.delete();

console.log('Cloud function job deleted successfully!');
});
}
4 changes: 2 additions & 2 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@apillon/sdk",
"description": "▶◀ Apillon SDK for NodeJS ▶◀",
"version": "3.3.0",
"version": "3.4.0",
"author": "Apillon",
"license": "MIT",
"main": "./dist/index.js",
Expand All @@ -14,7 +14,7 @@
"type": "git",
"url": "https://github.com/Apillon/sdk"
},
"homepage": "https://wiki.apillon.io",
"homepage": "https://wiki.apillon.io/build/5-apillon-sdk.html",
"declaration": true,
"files": [
"dist/**"
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from './modules/identity/identity';
export * from './modules/computing/computing';
export * from './modules/social/social';
export * from './modules/project/project';
export * from './modules/cloud-functions/cloud-functions';
54 changes: 54 additions & 0 deletions packages/sdk/src/modules/cloud-functions/cloud-function-job.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ApillonApi } from '../../lib/apillon-api';
import { ApillonModel } from '../../lib/apillon';
import { ApillonLogger } from '../../lib/apillon-logger';
import { JobStatus } from '../../types/cloud-functions';

export class CloudFunctionJob extends ApillonModel {
/**
* Unique identifier of the cloud function.
*/
public functionUuid: string = null;

/**
* Name of the job.
*/
public name: string = null;

/**
* CID of the script to be executed by the job.
*/
public scriptCid: string = null;

/**
* Number of processors to use for the job.
*/
public slots: number = null;

/**
* Status of the job.
*/
public jobStatus: JobStatus = null;

constructor(uuid: string, data?: Partial<CloudFunctionJob>) {
super(uuid);
this.API_PREFIX = `/cloud-functions/jobs/${uuid}`;
this.populate(data);
}

/**
* Deletes a specific job.
* @returns {Promise<void>}
*/
public async delete(): Promise<void> {
await ApillonApi.delete<void>(this.API_PREFIX);
ApillonLogger.log(`Job with UUID: ${this.uuid} successfully deleted`);
}

protected override serializeFilter(key: string, value: any) {
const serialized = super.serializeFilter(key, value);
const enums = {
jobStatus: JobStatus[value],
};
return Object.keys(enums).includes(key) ? enums[key] : serialized;
}
}
Loading

0 comments on commit dc7cc39

Please sign in to comment.