feat: Support seeding pods and accounts

* feat(seeding): seed accounts and pods with seeded-pod-config.json

* feat(seeding): dry up SeededPodInitializer by using RegistrationManager directly and make compatible with version/3.0.0

* feat(seeding): update seed config files to version 3.0.0 context

* feat(seeding): simplify seeded-root config by importing pre-existing prefilled-root config

* feat(seeding): Add seeding as a default initializer, update seeded pod copy and guide, change seeded pod config to array

* feat(seeding): remove template info from seeded pod guide, use mockFs, code style nit, fix redlock test

* feat(seeding): remove old config file
This commit is contained in:
Adler Faulkner 2022-03-10 01:16:05 -08:00 committed by GitHub
parent 44dd56d4f6
commit c8d4bfec39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 192 additions and 6 deletions

View File

@ -110,6 +110,7 @@ to some commonly used settings:
| `--sparqlEndpoint, -s` | | URL of the SPARQL endpoint, when using a quadstore-based configuration. |
| `--showStackTrace, -t` | false | Enables detailed logging on error pages. |
| `--podConfigJson` | `./pod-config.json` | Path to the file that keeps track of dynamic Pod configurations. |
| `--seededPodConfigJson` | | Path to the file that keeps track of seeded Pod configurations. |
| `--mainModulePath, -m` | | Path from where Components.js will start its lookup when initializing configurations.
### 🧶 Custom configurations

View File

@ -4,6 +4,7 @@
"files-scs:config/app/init/initializers/base-url.json",
"files-scs:config/app/init/initializers/logger.json",
"files-scs:config/app/init/initializers/server.json",
"files-scs:config/app/init/initializers/seeded-pod.json",
"files-scs:config/app/init/initializers/version.json"
],
"@graph": [
@ -15,6 +16,7 @@
{ "@id": "urn:solid-server:default:LoggerInitializer" },
{ "@id": "urn:solid-server:default:BaseUrlVerifier" },
{ "@id": "urn:solid-server:default:ParallelInitializer" },
{ "@id": "urn:solid-server:default:SeededPodInitializer" },
{ "@id": "urn:solid-server:default:ServerInitializer" },
{ "@id": "urn:solid-server:default:ModuleVersionVerifier" }
]

View File

@ -0,0 +1,23 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^3.0.0/components/context.jsonld",
"@graph": [
{
"comment": "Separate manager from the RegistrationHandler in case registration is disabled.",
"@id": "urn:solid-server:default:SeededPodRegistrationManager",
"@type": "RegistrationManager",
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"args_webIdSuffix": "/profile/card#me",
"args_identifierGenerator": { "@id": "urn:solid-server:default:IdentifierGenerator" },
"args_ownershipValidator": { "@id": "urn:solid-server:auth:password:OwnershipValidator" },
"args_accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" },
"args_podManager": { "@id": "urn:solid-server:default:PodManager" }
},
{
"comment": "Initializer that instantiates all the seeded accounts and pods.",
"@id": "urn:solid-server:default:SeededPodInitializer",
"@type": "SeededPodInitializer",
"registrationManager": { "@id": "urn:solid-server:default:SeededPodRegistrationManager" },
"configFilePath": { "@id": "urn:solid-server:default:variable:seededPodConfigJson" },
}
]
}

View File

@ -57,6 +57,11 @@
"requiresArg": true,
"type": "string",
"describe": "Path to the file that keeps track of dynamic Pod configurations."
},
"seededPodConfigJson": {
"requiresArg": true,
"type": "string",
"describe": "Path to the file that will be used to seed pods."
}
},
"options": {

View File

@ -52,12 +52,19 @@
}
},
{
"CombinedSettingsResolver:_computers_key": "urn:solid-server:default:variable:AssetPathResolver",
"CombinedSettingsResolver:_computers_key": "urn:solid-server:default:variable:podConfigJson",
"CombinedSettingsResolver:_computers_value": {
"@type": "AssetPathExtractor",
"key": "podConfigJson",
"defaultPath": "./pod-config.json"
}
},
{
"CombinedSettingsResolver:_computers_key": "urn:solid-server:default:variable:seededPodConfigJson",
"CombinedSettingsResolver:_computers_value": {
"@type": "AssetPathExtractor",
"key": "seededPodConfigJson"
}
}
]
}

View File

@ -36,6 +36,11 @@
"comment": "Path to the JSON file used to store configuration for dynamic pods.",
"@id": "urn:solid-server:default:variable:podConfigJson",
"@type": "Variable"
},
{
"comment": "Path to the JSON file used to seed pods.",
"@id": "urn:solid-server:default:variable:seededPodConfigJson",
"@type": "Variable"
}
]
}

20
guides/seeding-pods.md Normal file
View File

@ -0,0 +1,20 @@
# How to seed Accounts and Pods
If you need to seed accounts and pods, set the `--seededPodConfigJson` option to a file such as `./seeded-pod-config.json` to set your desired accounts and pods. The contents of `./seeded-pod-config.json` (or whatever file name you choose) should be a JSON array whose entries are objects which include
`podName`, `email`, and `password`. For example:
```json
[
{
"podName": "example",
"email": "hello@example.com",
"password": "abc123"
}
]
```
You may optionally specify other parameters accepted by the `register` method of [RegistrationManager](https://github.com/solid/community-server/blob/3b353affb1f0919fdcb66172364234eb59c2e3f6/src/identity/interaction/email-password/util/RegistrationManager.ts#L173). For example:
To use a pre-existing wedId:
```json
createWebId: false,
webId: "https://pod.inrupt.com/example/profile/card#me"
```

View File

@ -207,6 +207,7 @@ export * from './init/ConfigPodInitializer';
export * from './init/ContainerInitializer';
export * from './init/Initializer';
export * from './init/LoggerInitializer';
export * from './init/SeededPodInitializer';
export * from './init/ServerInitializer';
export * from './init/ModuleVersionVerifier';

View File

@ -0,0 +1,52 @@
import { promises as fsPromises } from 'fs';
import type { RegistrationManager } from '../identity/interaction/email-password/util/RegistrationManager';
import { getLoggerFor } from '../logging/LogUtil';
import { Initializer } from './Initializer';
/**
* Uses a {@link RegistrationManager} to initialize accounts and pods
* for all seeded pods. Reads the pod settings from seededPodConfigJson.
*/
export class SeededPodInitializer extends Initializer {
protected readonly logger = getLoggerFor(this);
private readonly registrationManager: RegistrationManager;
private readonly configFilePath: string | null;
public constructor(registrationManager: RegistrationManager, configFilePath: string | null) {
super();
this.registrationManager = registrationManager;
this.configFilePath = configFilePath;
}
public async handle(): Promise<void> {
if (!this.configFilePath) {
return;
}
const configText = await fsPromises.readFile(this.configFilePath, 'utf8');
const configuration: NodeJS.Dict<unknown>[] = JSON.parse(configText);
let count = 0;
for await (const input of configuration) {
const config = {
confirmPassword: input.password,
createPod: true,
createWebId: true,
register: true,
...input,
};
this.logger.info(`Initializing pod ${input.podName}`);
// Validate the input JSON
const validated = this.registrationManager.validateInput(config, true);
this.logger.debug(`Validated input: ${JSON.stringify(validated)}`);
// Register and/or create a pod as requested. Potentially does nothing if all booleans are false.
await this.registrationManager.register(validated, true);
this.logger.info(`Initialized seeded pod and account for "${input.podName}".`);
count += 1;
}
this.logger.info(`Initialized ${count} seeded pods.`);
}
}

View File

@ -18,9 +18,14 @@ export class AssetPathExtractor extends SettingsExtractor {
public async handle(args: Settings): Promise<unknown> {
const path = args[this.key] ?? this.defaultPath;
if (path) {
if (typeof path !== 'string') {
throw new Error(`Invalid ${this.key} argument`);
}
return resolveAssetPath(path);
}
return null;
}
}

View File

@ -48,5 +48,6 @@ export function getDefaultVariables(port: number, baseUrl?: string): Record<stri
'urn:solid-server:default:variable:port': port,
'urn:solid-server:default:variable:loggingLevel': 'off',
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:seededPodConfigJson': null,
};
}

View File

@ -17,6 +17,7 @@ describe('A server with the Solid WebSockets API behind a proxy', (): void => {
getTestConfigPath('server-without-auth.json'),
getDefaultVariables(port, 'https://example.pod/'),
) as App;
await app.start();
});

View File

@ -10,6 +10,8 @@
"files-scs:config/http/static/default.json",
"files-scs:config/identity/access/public.json",
"files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/token.json",
"files-scs:config/identity/pod/static.json",
"files-scs:config/ldp/authentication/debug-auth-header.json",
"files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json",

View File

@ -8,6 +8,9 @@
"files-scs:config/http/middleware/no-websockets.json",
"files-scs:config/http/server-factory/no-websockets.json",
"files-scs:config/http/static/default.json",
"files-scs:config/identity/handler/account-store/default.json",
"files-scs:config/identity/ownership/unsafe-no-check.json",
"files-scs:config/identity/pod/static.json",
"files-scs:config/ldp/authentication/debug-auth-header.json",
"files-scs:config/ldp/authorization/allow-all.json",
"files-scs:config/ldp/handler/default.json",

View File

@ -8,6 +8,9 @@
"files-scs:config/http/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.json",
"files-scs:config/http/static/default.json",
"files-scs:config/identity/handler/account-store/default.json",
"files-scs:config/identity/ownership/unsafe-no-check.json",
"files-scs:config/identity/pod/static.json",
"files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/allow-all.json",
"files-scs:config/ldp/handler/default.json",

View File

@ -62,6 +62,7 @@ describe('AppRunner', (): void => {
'urn:solid-server:default:variable:rootFilePath': '/var/cwd/',
'urn:solid-server:default:variable:showStackTrace': false,
'urn:solid-server:default:variable:podConfigJson': '/var/cwd/pod-config.json',
'urn:solid-server:default:variable:seededPodConfigJson': '/var/cwd/seeded-pod-config.json',
};
const createdApp = await new AppRunner().create(
{
@ -99,6 +100,7 @@ describe('AppRunner', (): void => {
'urn:solid-server:default:variable:rootFilePath': '/var/cwd/',
'urn:solid-server:default:variable:showStackTrace': false,
'urn:solid-server:default:variable:podConfigJson': '/var/cwd/pod-config.json',
'urn:solid-server:default:variable:seededPodConfigJson': '/var/cwd/seeded-pod-config.json',
};
await new AppRunner().run(
{
@ -166,6 +168,7 @@ describe('AppRunner', (): void => {
'-s', 'http://localhost:5000/sparql',
'-t',
'--podConfigJson', '/different-path.json',
'--seededPodConfigJson', '/different-path.json',
];
process.argv = argvParameters;

View File

@ -0,0 +1,47 @@
import { promises as fsPromises } from 'fs';
import type { RegistrationManager } from '../../../src/identity/interaction/email-password/util/RegistrationManager';
import { SeededPodInitializer } from '../../../src/init/SeededPodInitializer';
import { mockFs } from '../../util/Util';
jest.mock('fs');
describe('A SeededPodInitializer', (): void => {
const dummyConfig = JSON.stringify([
{
podName: 'example',
email: 'hello@example.com',
password: 'abc123',
},
{
podName: 'example2',
email: 'hello2@example.com',
password: '123abc',
},
]);
let registrationManager: RegistrationManager;
let configFilePath: string | null;
beforeEach(async(): Promise<void> => {
configFilePath = './seeded-pod-config.json';
registrationManager = {
validateInput: jest.fn((input): any => input),
register: jest.fn(),
} as any;
mockFs('/');
await fsPromises.writeFile(configFilePath, dummyConfig);
});
it('does not generate any accounts or pods if no config file is specified.', async(): Promise<void> => {
configFilePath = null;
await new SeededPodInitializer(registrationManager, configFilePath).handle();
expect(registrationManager.validateInput).not.toHaveBeenCalled();
expect(registrationManager.register).not.toHaveBeenCalled();
});
it('generates an account and a pod for every entry in the seeded pod configuration.', async(): Promise<void> => {
await new SeededPodInitializer(registrationManager, configFilePath).handle();
expect(registrationManager.validateInput).toHaveBeenCalledTimes(2);
expect(registrationManager.register).toHaveBeenCalledTimes(2);
});
});

View File

@ -25,4 +25,9 @@ describe('An AssetPathExtractor', (): void => {
resolver = new AssetPathExtractor('path', '/root');
await expect(resolver.handle({ otherPath: '/var/data' })).resolves.toBe('/root');
});
it('returns null if not default value or default is provided.', async(): Promise<void> => {
resolver = new AssetPathExtractor('path');
await expect(resolver.handle({ otherPath: '/var/data' })).resolves.toBeNull();
});
});