dev: begin adding new driver call method

This commit is contained in:
KernelDeimos 2024-07-24 01:51:35 -04:00 committed by Eric Dubé
parent e0d30f041b
commit d5ec40078f
10 changed files with 124 additions and 37 deletions

View File

@ -321,7 +321,13 @@ module.exports = class APIError {
}, },
'no_implementation_available': { 'no_implementation_available': {
status: 502, 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': { 'method_not_found': {
status: 404, status: 404,

View File

@ -65,6 +65,7 @@ const implicit_user_app_permissions = [
const hardcoded_user_group_permissions = { const hardcoded_user_group_permissions = {
system: { system: {
'b7220104-7905-4985-b996-649fdcdb3c8f': { 'b7220104-7905-4985-b996-649fdcdb3c8f': {
'service:helloworld:ii:helloworld': {},
'driver:puter-kvstore': { 'driver:puter-kvstore': {
$: 'json-address', $: 'json-address',
path: '/admin/.policy/drivers.json', path: '/admin/.policy/drivers.json',
@ -87,6 +88,7 @@ const hardcoded_user_group_permissions = {
}, },
}, },
'78b1b1dd-c959-44d2-b02c-8735671f9997': { '78b1b1dd-c959-44d2-b02c-8735671f9997': {
'service:helloworld:ii:helloworld': {},
'driver:puter-kvstore': { 'driver:puter-kvstore': {
$: 'json-address', $: 'json-address',
path: '/admin/.policy/drivers.json', path: '/admin/.policy/drivers.json',

View File

@ -58,7 +58,7 @@ module.exports = eggspress('/drivers/call', {
const interface_name = req.body.interface; const interface_name = req.body.interface;
const test_mode = req.body.test_mode; 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) ? await _handle_multipart(req)
: req.body.args; : req.body.args;
@ -66,7 +66,11 @@ module.exports = eggspress('/drivers/call', {
if ( test_mode ) context = context.sub({ test_mode: true }); if ( test_mode ) context = context.sub({ test_mode: true });
const result = await context.arun(async () => { 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); _respond(res, result);

View File

@ -60,8 +60,11 @@ router.post('/getItem', auth, express.json(), async (req, res, next)=>{
const svc_driver = Context.get('services').get('driver'); const svc_driver = Context.get('services').get('driver');
let driver_result; let driver_result;
try { try {
const driver_response = await svc_driver.call( const driver_response = await svc_driver.call({
'puter-kvstore', 'get', { key: req.body.key }); iface: 'puter-kvstore',
method: 'get',
args: { key: req.body.key },
});
if ( ! driver_response.success ) { if ( ! driver_response.success ) {
throw new Error(driver_response.error?.message ?? 'Unknown error'); throw new Error(driver_response.error?.message ?? 'Unknown error');
} }

View File

@ -72,11 +72,14 @@ router.post('/setItem', auth, express.json(), async (req, res, next)=>{
const svc_driver = Context.get('services').get('driver'); const svc_driver = Context.get('services').get('driver');
let driver_result; let driver_result;
try { try {
const driver_response = await svc_driver.call( const driver_response = await svc_driver.call({
'puter-kvstore', 'set', { iface: 'puter-kvstore',
method: 'set',
args: {
key: req.body.key, key: req.body.key,
value: req.body.value, value: req.body.value,
}); },
});
if ( ! driver_response.success ) { if ( ! driver_response.success ) {
throw new Error(driver_response.error?.message ?? 'Unknown error'); throw new Error(driver_response.error?.message ?? 'Unknown error');
} }

View File

@ -201,16 +201,23 @@ class DefaultUserService extends BaseService {
const actor = await Actor.create(UserActorType, { user }); const actor = await Actor.create(UserActorType, { user });
return await Context.get().sub({ actor }).arun(async () => { return await Context.get().sub({ actor }).arun(async () => {
const svc_driver = this.services.get('driver'); const svc_driver = this.services.get('driver');
const driver_response = await svc_driver.call( const driver_response = await svc_driver.call({
'puter-kvstore', 'get', { key: 'tmp_password' }); iface: 'puter-kvstore',
method: 'get',
args: { key: 'tmp_password' },
});
if ( driver_response.result ) return driver_response.result; if ( driver_response.result ) return driver_response.result;
const tmp_password = require('crypto').randomBytes(4).toString('hex'); const tmp_password = require('crypto').randomBytes(4).toString('hex');
await svc_driver.call( await svc_driver.call({
'puter-kvstore', 'set', { iface: 'puter-kvstore',
method: 'set',
args: {
key: 'tmp_password', key: 'tmp_password',
value: tmp_password }); value: tmp_password,
}
});
return tmp_password; return tmp_password;
}); });
} }
@ -223,10 +230,14 @@ class DefaultUserService extends BaseService {
const tmp_password = require('crypto').randomBytes(4).toString('hex'); const tmp_password = require('crypto').randomBytes(4).toString('hex');
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const password_hashed = await bcrypt.hash(tmp_password, 8); const password_hashed = await bcrypt.hash(tmp_password, 8);
await svc_driver.call( await svc_driver.call({
'puter-kvstore', 'set', { iface: 'puter-kvstore',
method: 'set',
args: {
key: 'tmp_password', key: 'tmp_password',
value: tmp_password }); value: tmp_password,
}
});
await db.write( await db.write(
`UPDATE user SET password = ? WHERE id = ?`, `UPDATE user SET password = ? WHERE id = ?`,
[ [

View File

@ -2,16 +2,12 @@ const BaseService = require("./BaseService");
class HelloWorldService extends BaseService { class HelloWorldService extends BaseService {
static IMPLEMENTS = { static IMPLEMENTS = {
['driver-metadata']: { ['version']: {
get_response_meta () { get_version () {
return { return 'v1.0.0';
driver: 'hello-world',
driver_version: 'v1.0.0',
driver_interface: 'helloworld',
};
} }
}, },
helloworld: { ['hello-world']: {
async greet ({ subject }) { async greet ({ subject }) {
if ( subject ) { if ( subject ) {
return `Hello, ${subject}!`; return `Hello, ${subject}!`;

View File

@ -36,6 +36,10 @@ class MapCollection extends AdvancedBase {
return this.kv.get(this._mk_key(key)); return this.kv.get(this._mk_key(key));
} }
exists (key) {
return this.kv.exists(this._mk_key(key));
}
set (key, value) { set (key, value) {
return this.kv.set(this._mk_key(key), value); return this.kv.set(this._mk_key(key), value);
} }

View File

@ -81,6 +81,7 @@ class DriverService extends BaseService {
return this.interface_to_implementation[interface_name]; return this.interface_to_implementation[interface_name];
} }
return;
this.log.noticeme('HERE IT IS'); this.log.noticeme('HERE IT IS');
const options = this.services.get_implementors(interface_name); const options = this.services.get_implementors(interface_name);
this.log.info('test', { options }); this.log.info('test', { options });
@ -88,16 +89,17 @@ class DriverService extends BaseService {
return options[0]; return options[0];
} }
async call (...a) { async call (o) {
try { try {
return await this._call(...a); return await this._call(o);
} catch ( e ) { } catch ( e ) {
console.error(e);
return this._driver_response_from_error(e); return this._driver_response_from_error(e);
} }
} }
async _call (interface_name, method, args) { async _call ({ driver, iface, method, args }) {
const processed_args = await this._process_args(interface_name, method, args); const processed_args = await this._process_args(iface, method, args);
if ( Context.get('test_mode') ) { if ( Context.get('test_mode') ) {
processed_args.test_mode = true; processed_args.test_mode = true;
} }
@ -110,18 +112,44 @@ class DriverService extends BaseService {
const services = Context.get('services'); const services = Context.get('services');
const svc_permission = services.get('permission'); 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); const options = PermissionUtil.reading_to_options(reading);
if ( ! (options.length > 0) ) { if ( ! (options.length > 0) ) {
throw APIError.create('permission_denied'); throw APIError.create('permission_denied');
} }
const svc_registry = this.services.get('registry'); const instance = this.get_default_implementation(iface);
const c_interfaces = svc_registry.get('interfaces');
const instance = this.get_default_implementation(interface_name);
if ( ! instance ) { if ( ! instance ) {
throw APIError.create('no_implementation_available', null, { interface_name }) throw APIError.create('no_implementation_available', null, { iface })
} }
const meta = await (async () => { const meta = await (async () => {
if ( instance instanceof Driver ) { if ( instance instanceof Driver ) {
@ -142,7 +170,7 @@ class DriverService extends BaseService {
result = await instance.impl[method](processed_args); result = await instance.impl[method](processed_args);
} }
if ( result instanceof TypedValue ) { if ( result instanceof TypedValue ) {
const interface_ = c_interfaces.get(interface_name); const interface_ = c_interfaces.get(iface);
let desired_type = interface_.methods[method] let desired_type = interface_.methods[method]
.result_choices[0].type; .result_choices[0].type;
const svc_coercion = services.get('coercion'); const svc_coercion = services.get('coercion');
@ -151,8 +179,9 @@ class DriverService extends BaseService {
} }
return { success: true, ...meta, result }; return { success: true, ...meta, result };
} catch ( e ) { } catch ( e ) {
console.error(e);
let for_user = (e instanceof APIError) || (e instanceof DriverError); 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, source: e,
trace: true, trace: true,
// TODO: alarm will not be suitable for all errors. // TODO: alarm will not be suitable for all errors.
@ -165,6 +194,35 @@ class DriverService extends BaseService {
} }
} }
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) { async _driver_response_from_error (e, meta) {
let serializable = (e instanceof APIError) || (e instanceof DriverError); let serializable = (e instanceof APIError) || (e instanceof DriverError);
if ( serializable ) { if ( serializable ) {

View File

@ -70,7 +70,7 @@ const ENTITY_STORAGE_INTERFACE = {
} }
module.exports = { module.exports = {
'helloworld': { 'hello-world': {
description: 'A simple driver that returns a greeting.', description: 'A simple driver that returns a greeting.',
methods: { methods: {
greet: { greet: {