From d5ec40078fb07314a0f8bbcac9657261ccc95ac5 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Wed, 24 Jul 2024 01:51:35 -0400 Subject: [PATCH] dev: begin adding new driver call method --- src/backend/src/api/APIError.js | 8 +- src/backend/src/data/hardcoded-permissions.js | 2 + src/backend/src/routers/drivers/call.js | 8 +- src/backend/src/routers/kvstore/getItem.js | 7 +- src/backend/src/routers/kvstore/setItem.js | 9 +- .../src/services/DefaultUserService.js | 27 ++++-- src/backend/src/services/HelloWorldService.js | 12 +-- src/backend/src/services/RegistryService.js | 4 + .../src/services/drivers/DriverService.js | 82 ++++++++++++++++--- .../src/services/drivers/interfaces.js | 2 +- 10 files changed, 124 insertions(+), 37 deletions(-) diff --git a/src/backend/src/api/APIError.js b/src/backend/src/api/APIError.js index 736d7621..ed188c07 100644 --- a/src/backend/src/api/APIError.js +++ b/src/backend/src/api/APIError.js @@ -321,7 +321,13 @@ module.exports = class APIError { }, 'no_implementation_available': { status: 502, - message: ({ interface_name }) => `No implementation available for interface ${quot(interface_name)}`, + message: ({ + iface, + interface_name, + driver + }) => `No implementation available for ` + + (iface ?? interface_name) ? 'interface' : 'driver' + + ' ' + quot(iface ?? interface_name ?? driver) + '.', }, 'method_not_found': { status: 404, diff --git a/src/backend/src/data/hardcoded-permissions.js b/src/backend/src/data/hardcoded-permissions.js index 575c48a1..939ae1cb 100644 --- a/src/backend/src/data/hardcoded-permissions.js +++ b/src/backend/src/data/hardcoded-permissions.js @@ -65,6 +65,7 @@ const implicit_user_app_permissions = [ const hardcoded_user_group_permissions = { system: { 'b7220104-7905-4985-b996-649fdcdb3c8f': { + 'service:helloworld:ii:helloworld': {}, 'driver:puter-kvstore': { $: 'json-address', path: '/admin/.policy/drivers.json', @@ -87,6 +88,7 @@ const hardcoded_user_group_permissions = { }, }, '78b1b1dd-c959-44d2-b02c-8735671f9997': { + 'service:helloworld:ii:helloworld': {}, 'driver:puter-kvstore': { $: 'json-address', path: '/admin/.policy/drivers.json', diff --git a/src/backend/src/routers/drivers/call.js b/src/backend/src/routers/drivers/call.js index 7a83b095..8d0ff7aa 100644 --- a/src/backend/src/routers/drivers/call.js +++ b/src/backend/src/routers/drivers/call.js @@ -58,7 +58,7 @@ module.exports = eggspress('/drivers/call', { const interface_name = req.body.interface; const test_mode = req.body.test_mode; - const params = req.headers['content-type'].includes('multipart/form-data') + const args = req.headers['content-type'].includes('multipart/form-data') ? await _handle_multipart(req) : req.body.args; @@ -66,7 +66,11 @@ module.exports = eggspress('/drivers/call', { if ( test_mode ) context = context.sub({ test_mode: true }); const result = await context.arun(async () => { - return await svc_driver.call(interface_name, req.body.method, params); + return await svc_driver.call({ + iface: interface_name, + method: req.body.method, + args + }); }); _respond(res, result); diff --git a/src/backend/src/routers/kvstore/getItem.js b/src/backend/src/routers/kvstore/getItem.js index 650a23ea..e1d564c2 100644 --- a/src/backend/src/routers/kvstore/getItem.js +++ b/src/backend/src/routers/kvstore/getItem.js @@ -60,8 +60,11 @@ router.post('/getItem', auth, express.json(), async (req, res, next)=>{ const svc_driver = Context.get('services').get('driver'); let driver_result; try { - const driver_response = await svc_driver.call( - 'puter-kvstore', 'get', { key: req.body.key }); + const driver_response = await svc_driver.call({ + iface: 'puter-kvstore', + method: 'get', + args: { key: req.body.key }, + }); if ( ! driver_response.success ) { throw new Error(driver_response.error?.message ?? 'Unknown error'); } diff --git a/src/backend/src/routers/kvstore/setItem.js b/src/backend/src/routers/kvstore/setItem.js index c52b3460..86a48d4a 100644 --- a/src/backend/src/routers/kvstore/setItem.js +++ b/src/backend/src/routers/kvstore/setItem.js @@ -72,11 +72,14 @@ router.post('/setItem', auth, express.json(), async (req, res, next)=>{ const svc_driver = Context.get('services').get('driver'); let driver_result; try { - const driver_response = await svc_driver.call( - 'puter-kvstore', 'set', { + const driver_response = await svc_driver.call({ + iface: 'puter-kvstore', + method: 'set', + args: { key: req.body.key, value: req.body.value, - }); + }, + }); if ( ! driver_response.success ) { throw new Error(driver_response.error?.message ?? 'Unknown error'); } diff --git a/src/backend/src/services/DefaultUserService.js b/src/backend/src/services/DefaultUserService.js index 7cdc2cf5..e9697e77 100644 --- a/src/backend/src/services/DefaultUserService.js +++ b/src/backend/src/services/DefaultUserService.js @@ -201,16 +201,23 @@ class DefaultUserService extends BaseService { const actor = await Actor.create(UserActorType, { user }); return await Context.get().sub({ actor }).arun(async () => { const svc_driver = this.services.get('driver'); - const driver_response = await svc_driver.call( - 'puter-kvstore', 'get', { key: 'tmp_password' }); + const driver_response = await svc_driver.call({ + iface: 'puter-kvstore', + method: 'get', + args: { key: 'tmp_password' }, + }); if ( driver_response.result ) return driver_response.result; const tmp_password = require('crypto').randomBytes(4).toString('hex'); - await svc_driver.call( - 'puter-kvstore', 'set', { + await svc_driver.call({ + iface: 'puter-kvstore', + method: 'set', + args: { key: 'tmp_password', - value: tmp_password }); + value: tmp_password, + } + }); return tmp_password; }); } @@ -223,10 +230,14 @@ class DefaultUserService extends BaseService { const tmp_password = require('crypto').randomBytes(4).toString('hex'); const bcrypt = require('bcrypt'); const password_hashed = await bcrypt.hash(tmp_password, 8); - await svc_driver.call( - 'puter-kvstore', 'set', { + await svc_driver.call({ + iface: 'puter-kvstore', + method: 'set', + args: { key: 'tmp_password', - value: tmp_password }); + value: tmp_password, + } + }); await db.write( `UPDATE user SET password = ? WHERE id = ?`, [ diff --git a/src/backend/src/services/HelloWorldService.js b/src/backend/src/services/HelloWorldService.js index efc0a568..aa233ca0 100644 --- a/src/backend/src/services/HelloWorldService.js +++ b/src/backend/src/services/HelloWorldService.js @@ -2,16 +2,12 @@ const BaseService = require("./BaseService"); class HelloWorldService extends BaseService { static IMPLEMENTS = { - ['driver-metadata']: { - get_response_meta () { - return { - driver: 'hello-world', - driver_version: 'v1.0.0', - driver_interface: 'helloworld', - }; + ['version']: { + get_version () { + return 'v1.0.0'; } }, - helloworld: { + ['hello-world']: { async greet ({ subject }) { if ( subject ) { return `Hello, ${subject}!`; diff --git a/src/backend/src/services/RegistryService.js b/src/backend/src/services/RegistryService.js index cd966727..8d28b30b 100644 --- a/src/backend/src/services/RegistryService.js +++ b/src/backend/src/services/RegistryService.js @@ -35,6 +35,10 @@ class MapCollection extends AdvancedBase { get (key) { return this.kv.get(this._mk_key(key)); } + + exists (key) { + return this.kv.exists(this._mk_key(key)); + } set (key, value) { return this.kv.set(this._mk_key(key), value); diff --git a/src/backend/src/services/drivers/DriverService.js b/src/backend/src/services/drivers/DriverService.js index 03251405..0562860d 100644 --- a/src/backend/src/services/drivers/DriverService.js +++ b/src/backend/src/services/drivers/DriverService.js @@ -81,6 +81,7 @@ class DriverService extends BaseService { return this.interface_to_implementation[interface_name]; } + return; this.log.noticeme('HERE IT IS'); const options = this.services.get_implementors(interface_name); this.log.info('test', { options }); @@ -88,16 +89,17 @@ class DriverService extends BaseService { return options[0]; } - async call (...a) { + async call (o) { try { - return await this._call(...a); + return await this._call(o); } catch ( e ) { + console.error(e); return this._driver_response_from_error(e); } } - async _call (interface_name, method, args) { - const processed_args = await this._process_args(interface_name, method, args); + async _call ({ driver, iface, method, args }) { + const processed_args = await this._process_args(iface, method, args); if ( Context.get('test_mode') ) { processed_args.test_mode = true; } @@ -110,18 +112,44 @@ class DriverService extends BaseService { const services = Context.get('services'); const svc_permission = services.get('permission'); - const reading = await svc_permission.scan(actor, `driver:${interface_name}:${method}`); + + const svc_registry = this.services.get('registry'); + const c_interfaces = svc_registry.get('interfaces'); + + driver = driver ?? iface; + + const driver_service_exists = (() => { + return this.services.has(driver) && + this.services.get(driver).list_traits() + .includes(iface); + })(); + if ( driver_service_exists ) { + const service = this.services.get(driver); + const reading = await svc_permission.scan( + actor, + PermissionUtil.join('driver', driver, 'ii', iface), + ); + const options = PermissionUtil.reading_to_options(reading); + if ( options.length > 0 ) { + return await this.call_new_({ + service_name: driver, + service, + method, + args: processed_args, + iface, + }); + } + } + + const reading = await svc_permission.scan(actor, `driver:${iface}:${method}`); const options = PermissionUtil.reading_to_options(reading); if ( ! (options.length > 0) ) { throw APIError.create('permission_denied'); } - const svc_registry = this.services.get('registry'); - const c_interfaces = svc_registry.get('interfaces'); - - const instance = this.get_default_implementation(interface_name); + const instance = this.get_default_implementation(iface); if ( ! instance ) { - throw APIError.create('no_implementation_available', null, { interface_name }) + throw APIError.create('no_implementation_available', null, { iface }) } const meta = await (async () => { if ( instance instanceof Driver ) { @@ -142,7 +170,7 @@ class DriverService extends BaseService { result = await instance.impl[method](processed_args); } if ( result instanceof TypedValue ) { - const interface_ = c_interfaces.get(interface_name); + const interface_ = c_interfaces.get(iface); let desired_type = interface_.methods[method] .result_choices[0].type; const svc_coercion = services.get('coercion'); @@ -151,8 +179,9 @@ class DriverService extends BaseService { } return { success: true, ...meta, result }; } catch ( e ) { + console.error(e); let for_user = (e instanceof APIError) || (e instanceof DriverError); - if ( ! for_user ) this.errors.report(`driver:${interface_name}:${method}`, { + if ( ! for_user ) this.errors.report(`driver:${iface}:${method}`, { source: e, trace: true, // TODO: alarm will not be suitable for all errors. @@ -164,6 +193,35 @@ class DriverService extends BaseService { return this._driver_response_from_error(e, meta); } } + + async call_new_ ({ + service_name, + service, method, args, + iface, + }) { + const svc_registry = this.services.get('registry'); + const c_interfaces = svc_registry.get('interfaces'); + let result = await service.as(iface)[method](args); + if ( result instanceof TypedValue ) { + const interface_ = c_interfaces.get(iface); + let desired_type = interface_.methods[method] + .result_choices[0].type; + const svc_coercion = services.get('coercion'); + result = await svc_coercion.coerce(desired_type, result); + } + const service_meta = {}; + if ( service.list_traits().includes('version') ) { + service_meta.version = service.as('version').get_version(); + } + return { + success: true, + service: { + ...service_meta, + name: service_name, + }, + result + }; + } async _driver_response_from_error (e, meta) { let serializable = (e instanceof APIError) || (e instanceof DriverError); diff --git a/src/backend/src/services/drivers/interfaces.js b/src/backend/src/services/drivers/interfaces.js index 10a95825..9f0c6d31 100644 --- a/src/backend/src/services/drivers/interfaces.js +++ b/src/backend/src/services/drivers/interfaces.js @@ -70,7 +70,7 @@ const ENTITY_STORAGE_INTERFACE = { } module.exports = { - 'helloworld': { + 'hello-world': { description: 'A simple driver that returns a greeting.', methods: { greet: {