feat(server): YAML config file support (#7894)

* test(server): Load config from yaml

* docs: YAML config support

* feat(server): YAML config file support

* fix format

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
bo0tzz
2024-03-12 16:29:49 +01:00
committed by GitHub
parent 1683bb75e1
commit 72f9295490
5 changed files with 58 additions and 7 deletions

View File

@@ -17,6 +17,7 @@ import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/com
import { CronExpression } from '@nestjs/schedule';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
import { load as loadYaml } from 'js-yaml';
import * as _ from 'lodash';
import { Subject } from 'rxjs';
import { QueueName } from '../job/job.constants';
@@ -341,19 +342,19 @@ export class SystemConfigCore {
if (force || !this.configCache) {
try {
const file = await this.repository.readFile(filepath);
const json = JSON.parse(file.toString());
const config = loadYaml(file.toString()) as any;
const overrides: SystemConfigEntity<SystemConfigValue>[] = [];
for (const key of Object.values(SystemConfigKey)) {
const value = _.get(json, key);
this.unsetDeep(json, key);
const value = _.get(config, key);
this.unsetDeep(config, key);
if (value !== undefined) {
overrides.push({ key, value });
}
}
if (!_.isEmpty(json)) {
this.logger.warn(`Unknown keys found: ${JSON.stringify(json, null, 2)}`);
if (!_.isEmpty(config)) {
this.logger.warn(`Unknown keys found: ${JSON.stringify(config, null, 2)}`);
}
this.configCache = overrides;

View File

@@ -209,7 +209,7 @@ describe(SystemConfigService.name, () => {
await expect(sut.getConfig()).resolves.toEqual(updatedConfig);
});
it('should load the config from a file', async () => {
it('should load the config from a json file', async () => {
process.env.IMMICH_CONFIG_FILE = 'immich-config.json';
const partialConfig = {
ffmpeg: { crf: 30 },
@@ -224,6 +224,25 @@ describe(SystemConfigService.name, () => {
expect(configMock.readFile).toHaveBeenCalledWith('immich-config.json');
});
it('should load the config from a yaml file', async () => {
process.env.IMMICH_CONFIG_FILE = 'immich-config.yaml';
const partialConfig = `
ffmpeg:
crf: 30
oauth:
autoLaunch: true
trash:
days: 10
user:
deleteDelay: 15
`;
configMock.readFile.mockResolvedValue(partialConfig);
await expect(sut.getConfig()).resolves.toEqual(updatedConfig);
expect(configMock.readFile).toHaveBeenCalledWith('immich-config.yaml');
});
it('should accept an empty configuration file', async () => {
process.env.IMMICH_CONFIG_FILE = 'immich-config.json';
configMock.readFile.mockResolvedValue(JSON.stringify({}));
@@ -242,6 +261,17 @@ describe(SystemConfigService.name, () => {
expect(config.machineLearning.url).toEqual('immich_machine_learning');
});
it('should warn for unknown options in yaml', async () => {
process.env.IMMICH_CONFIG_FILE = 'immich-config.yaml';
const partialConfig = `
unknownOption: true
`;
configMock.readFile.mockResolvedValue(partialConfig);
await sut.getConfig();
expect(warnLog).toHaveBeenCalled();
});
const tests = [
{ should: 'validate numbers', config: { ffmpeg: { crf: 'not-a-number' } } },
{ should: 'validate booleans', config: { oauth: { enabled: 'invalid' } } },