Skip to content

Commit 8255b61

Browse files
authored
Merge pull request #12 from jembi/CU-86c19daeq_Add-validations-for-openhim-config
CU-86c19daeq_Add-validations-for-openhim-config
2 parents 4b29f8f + 96fef7e commit 8255b61

File tree

7 files changed

+115
-57
lines changed

7 files changed

+115
-57
lines changed

README.md

+24
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,27 @@ MINIO_SECRETE_KEY:
7777
``` bash
7878
MINIO_BUCKET=climate-mediator,
7979
```
80+
81+
## Creating Buckets
82+
83+
### Validation Rules for Creating a Bucket
84+
85+
When creating a bucket the name must follow these following rules:
86+
> Bucket names must be between 3 (min) and 63 (max) characters long.
87+
> Bucket names can consist only of lowercase letters, numbers, dots (.), and hyphens (-).
88+
> Bucket names must not start with the prefix xn--.
89+
> Bucket names must not end with the suffix -s3alias. This suffix is reserved for access point alias names.
90+
91+
### Enabling Automatic Bucket Creation Through API
92+
93+
To allow automatic creation of the bucket if it does not exist, include the createBucketIfNotExists query parameter and set it to true. This will ensure the bucket is created with the specified name if it is not already present.
94+
95+
```bash
96+
/upload?bucket=:name&createBucketIfNotExists=true
97+
```
98+
99+
This optional parameter simplifies the process by eliminating the need to manually create buckets beforehand.
100+
101+
### Enabling Automatic Bucket Creation Through OpenHIM Console
102+
103+
Navigate to `/mediators/urn:mediator:climate-mediator` and click the gear icon, next click the green button that states `Minio Buckets Registry` and add the bucket name and region to the two form elements that appear. Finally click `Save Changes`, the newly entered buckets should appear withing minio momentary.

src/index.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import express from 'express';
2-
import path from 'path';
32
import { getConfig } from './config/config';
43
import logger from './logger';
54
import routes from './routes/index';
6-
import { getRegisteredBuckets, setupMediator } from './openhim/openhim';
7-
import { createMinioBucketListeners, ensureBucketExists } from './utils/minioClient';
5+
import { getMediatorConfig, initializeBuckets, setupMediator } from './openhim/openhim';
6+
import { MinioBucketsRegistry } from './types/mediatorConfig';
87

98
const app = express();
109

@@ -15,15 +14,16 @@ app.listen(getConfig().port, async () => {
1514

1615
if (getConfig().runningMode !== 'testing' && getConfig().registerMediator) {
1716
await setupMediator();
18-
}
19-
20-
const buckets = await getRegisteredBuckets();
2117

22-
buckets.length === 0 && logger.warn('No buckets specified in the configuration');
23-
24-
for await (const { bucket, region } of buckets) {
25-
await ensureBucketExists(bucket, region, true);
18+
const mediatorConfig = await getMediatorConfig();
19+
if (mediatorConfig) {
20+
await initializeBuckets(
21+
mediatorConfig.config?.minio_buckets_registry as MinioBucketsRegistry[]
22+
);
23+
} else {
24+
logger.warn('Failed to fetch mediator config, skipping bucket initialization');
25+
}
26+
} else {
27+
logger.info('Running in testing mode, skipping mediator setup');
2628
}
27-
28-
createMinioBucketListeners(buckets.map((bucket) => bucket.bucket));
2929
});

src/openhim/mediatorConfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
{
4141
"param": "minio_buckets_registry",
4242
"displayName": "Minio Buckets Registry",
43-
"description": "The available Minio buckets and their configurations",
43+
"description": "The available Minio buckets and their configurations (Note: The names provided must be between 3 and 63 characters long, and can only contain lowercase letters, numbers, dots (.), and hyphens (-))",
4444
"type": "struct",
4545
"array": true,
4646
"template": [

src/openhim/openhim.ts

+64-32
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import logger from '../logger';
22
import { MediatorConfig, MinioBucketsRegistry } from '../types/mediatorConfig';
33
import { RequestOptions } from '../types/request';
4-
import { getConfig } from '../config/config';
4+
import { Config, getConfig } from '../config/config';
55
import axios, { AxiosError } from 'axios';
66
import https from 'https';
77
import { activateHeartbeat, fetchConfig, registerMediator } from 'openhim-mediator-utils';
88
import { Bucket, createMinioBucketListeners, ensureBucketExists } from '../utils/minioClient';
99
import path from 'path';
10+
import { validateBucketName } from '../utils/file-validators';
1011

1112
const { openhimUsername, openhimPassword, openhimMediatorUrl, trustSelfSigned, runningMode } =
1213
getConfig();
@@ -63,15 +64,8 @@ export const setupMediator = async () => {
6364
});
6465

6566
emitter.on('config', async (config: any) => {
66-
logger.info('Received config from OpenHIM');
67-
68-
const buckets = config.minio_buckets_registry as Bucket[];
69-
70-
for await (const { bucket, region } of buckets) {
71-
await ensureBucketExists(bucket, region, true);
72-
}
73-
74-
createMinioBucketListeners(buckets.map((bucket) => bucket.bucket));
67+
logger.debug('Received new configs from OpenHIM');
68+
await initializeBuckets(config.minio_buckets_registry);
7569
});
7670
});
7771
});
@@ -80,7 +74,42 @@ export const setupMediator = async () => {
8074
}
8175
};
8276

83-
async function getMediatorConfig(): Promise<MediatorConfig | null> {
77+
/**
78+
* Initializes the buckets based on the values in the mediator config
79+
* if the bucket is invalid, it will be removed from the config
80+
* otherwise, the bucket will be created if it doesn't exist
81+
* and the listeners will be created for the valid buckets
82+
*
83+
* @param mediatorConfig - The mediator config
84+
*/
85+
export async function initializeBuckets(buckets: MinioBucketsRegistry[]) {
86+
if (!buckets) {
87+
logger.error('No buckets found in mediator config');
88+
return;
89+
}
90+
91+
const validBuckets: string[] = [];
92+
const invalidBuckets: string[] = [];
93+
94+
for await (const { bucket, region } of buckets) {
95+
if (!validateBucketName(bucket)) {
96+
logger.error(`Invalid bucket name ${bucket}, skipping`);
97+
invalidBuckets.push(bucket);
98+
} else {
99+
await ensureBucketExists(bucket, region, true);
100+
validBuckets.push(bucket);
101+
}
102+
}
103+
104+
await createMinioBucketListeners(validBuckets);
105+
106+
if (invalidBuckets.length > 0) {
107+
await removeBucket(invalidBuckets);
108+
logger.info(`Removed ${invalidBuckets.length} invalid buckets`);
109+
}
110+
}
111+
112+
export async function getMediatorConfig(): Promise<MediatorConfig | null> {
84113
logger.debug('Fetching mediator config from OpenHIM');
85114
const mediatorConfig = resolveMediatorConfig();
86115
const openhimConfig = resolveOpenhimConfig(mediatorConfig.urn);
@@ -152,27 +181,6 @@ async function putMediatorConfig(mediatorUrn: string, mediatorConfig: MinioBucke
152181
}
153182
}
154183

155-
export async function getRegisteredBuckets(): Promise<Bucket[]> {
156-
if (runningMode === 'testing') {
157-
logger.info('Running in testing mode, reading buckets from ENV');
158-
const buckets = getConfig().minio.buckets.split(',');
159-
return buckets.map((bucket) => ({ bucket, region: '' }));
160-
}
161-
162-
logger.info('Fetching registered buckets from OpenHIM');
163-
const mediatorConfig = await getMediatorConfig();
164-
165-
if (!mediatorConfig) {
166-
return [];
167-
}
168-
169-
const buckets = mediatorConfig.config?.minio_buckets_registry as Bucket[];
170-
if (buckets) {
171-
return buckets;
172-
}
173-
return [];
174-
}
175-
176184
export async function registerBucket(bucket: string, region?: string) {
177185
// If we are in testing mode, we don't need to have the registered buckets persisted
178186
if (runningMode === 'testing') {
@@ -219,3 +227,27 @@ export async function registerBucket(bucket: string, region?: string) {
219227

220228
return true;
221229
}
230+
231+
export async function removeBucket(buckets: string[]) {
232+
const mediatorConfig = await getMediatorConfig();
233+
234+
if (!mediatorConfig) {
235+
logger.error('Mediator config not found in OpenHIM, unable to remove bucket');
236+
return false;
237+
}
238+
239+
const existingConfig = mediatorConfig.config;
240+
241+
if (existingConfig === undefined) {
242+
logger.error('Mediator config does not have a config section, unable to remove bucket');
243+
return false;
244+
}
245+
246+
const updatedConfig = existingConfig.minio_buckets_registry.filter(
247+
(b) => !buckets.includes(b.bucket)
248+
);
249+
250+
await putMediatorConfig(mediatorConfig.urn, updatedConfig);
251+
252+
return true;
253+
}

src/routes/index.ts

+4-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import express from 'express';
22
import multer from 'multer';
33
import { getConfig } from '../config/config';
4-
import { getCsvHeaders } from '../utils/file-validators';
4+
import { getCsvHeaders, validateBucketName } from '../utils/file-validators';
55
import logger from '../logger';
66
import fs from 'fs/promises';
77
import path from 'path';
@@ -101,15 +101,6 @@ const handleJsonFile = (file: Express.Multer.File): UploadResponse => {
101101
return createSuccessResponse('JSON_VALID', 'JSON file is valid - Future implementation');
102102
};
103103

104-
const validateBucketName = (bucket: string): boolean => {
105-
// Bucket names must be between 3 (min) and 63 (max) characters long.
106-
// Bucket names can consist only of lowercase letters, numbers, dots (.), and hyphens (-).
107-
// Bucket names must not start with the prefix xn--.
108-
// Bucket names must not end with the suffix -s3alias. This suffix is reserved for access point alias names.
109-
const regex = new RegExp(/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/);
110-
return regex.test(bucket);
111-
};
112-
113104
// Main route handler
114105
routes.post('/upload', upload.single('file'), async (req, res) => {
115106
try {
@@ -147,7 +138,9 @@ routes.post('/upload', upload.single('file'), async (req, res) => {
147138
? await handleCsvFile(file, bucket, region)
148139
: handleJsonFile(file);
149140

150-
createBucketIfNotExists && (await registerBucket(bucket, region));
141+
if (createBucketIfNotExists && getConfig().runningMode !== 'testing') {
142+
await registerBucket(bucket, region);
143+
}
151144

152145
const statusCode = response.status === 'success' ? 201 : 400;
153146
return res.status(statusCode).json(response);

src/utils/file-validators.ts

+9
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,12 @@ export function getCsvHeaders(file: Buffer) {
2222

2323
return columns;
2424
}
25+
26+
export function validateBucketName(bucket: string): boolean {
27+
// Bucket names must be between 3 (min) and 63 (max) characters long.
28+
// Bucket names can consist only of lowercase letters, numbers, dots (.), and hyphens (-).
29+
// Bucket names must not start with the prefix xn--.
30+
// Bucket names must not end with the suffix -s3alias. This suffix is reserved for access point alias names.
31+
const regex = new RegExp(/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/);
32+
return regex.test(bucket);
33+
}

src/utils/minioClient.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export async function uploadToMinio(
172172
export async function createMinioBucketListeners(listOfBuckets: string[]) {
173173
for (const bucket of listOfBuckets) {
174174
if (registeredBuckets.has(bucket)) {
175-
logger.info(`Bucket ${bucket} already registered`);
175+
logger.debug(`Bucket ${bucket} already registered`);
176176
continue;
177177
}
178178

0 commit comments

Comments
 (0)