mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
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:
parent
44dd56d4f6
commit
c8d4bfec39
@ -69,7 +69,7 @@ npm start -- # add parameters if needed
|
||||
```
|
||||
|
||||
### 📦 Running via Docker
|
||||
Docker allows you to run the server without having Node.js installed. Images are built on each tagged version and hosted on [Docker Hub](https://hub.docker.com/r/solidproject/community-server).
|
||||
Docker allows you to run the server without having Node.js installed. Images are built on each tagged version and hosted on [Docker Hub](https://hub.docker.com/r/solidproject/community-server).
|
||||
|
||||
```shell
|
||||
# Clone the repo to get access to the configs
|
||||
@ -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
|
||||
|
@ -10,7 +10,7 @@ it is always possible to not choose any of them and create your own custom versi
|
||||
|
||||
# How to use
|
||||
The easiest way to create a new config is by creating a JSON-LD file
|
||||
that imports one option from every component subfolder
|
||||
that imports one option from every component subfolder
|
||||
(such as either `allow-all.json` or `webacl.json` from `ldp/authorization`).
|
||||
In case none of the available options suffice, there are 2 other ways to handle this:
|
||||
|
||||
|
@ -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" }
|
||||
]
|
||||
|
23
config/app/init/initializers/seeded-pod.json
Normal file
23
config/app/init/initializers/seeded-pod.json
Normal 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" },
|
||||
}
|
||||
]
|
||||
}
|
@ -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": {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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
20
guides/seeding-pods.md
Normal 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"
|
||||
```
|
@ -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';
|
||||
|
||||
|
52
src/init/SeededPodInitializer.ts
Normal file
52
src/init/SeededPodInitializer.ts
Normal 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.`);
|
||||
}
|
||||
}
|
@ -18,9 +18,14 @@ export class AssetPathExtractor extends SettingsExtractor {
|
||||
|
||||
public async handle(args: Settings): Promise<unknown> {
|
||||
const path = args[this.key] ?? this.defaultPath;
|
||||
if (typeof path !== 'string') {
|
||||
throw new Error(`Invalid ${this.key} argument`);
|
||||
if (path) {
|
||||
if (typeof path !== 'string') {
|
||||
throw new Error(`Invalid ${this.key} argument`);
|
||||
}
|
||||
|
||||
return resolveAssetPath(path);
|
||||
}
|
||||
return resolveAssetPath(path);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
||||
|
47
test/unit/init/SeededPodInitializer.test.ts
Normal file
47
test/unit/init/SeededPodInitializer.test.ts
Normal 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);
|
||||
});
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user