Skip to content

Commit 7631d1c

Browse files
committed
feat: improve database calls and broker/vault integration
1 parent 3bd48d9 commit 7631d1c

File tree

11 files changed

+281
-152
lines changed

11 files changed

+281
-152
lines changed

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ Sidecar for rotating log files to objectstore.
77
1. Copy `setenv-tmpl.sh` to `setenv-local.sh`.
88
2. Modify cron to run every minute ("*/1 * * * *").
99
3. Change LOGROTATE_DIRECTORY to "logs". Add your OBJECT_STORAGE_ secrets.
10-
4. Start sidecar: `npm run start`
11-
5. Create sample log files: `./test/create-log-files.sh`
12-
6. View DB as cron executes: `sqlite3 ./logs/cron.db 'select * from logs'`
13-
7. Use https://min.io/docs/minio/linux/reference/minio-mc.html# to view files
14-
8. Stop and delete test files in objectstore
10+
4. Source env: `source ./setenv-local.sh`
11+
5. Start sidecar: `npm run start`
12+
6. Create sample log files: `./test/create-log-files.sh`
13+
7. View DB as cron executes: `sqlite3 ./logs/cron.db 'select * from logs'`
14+
8. Use https://min.io/docs/minio/linux/reference/minio-mc.html# to view files
15+
9. Stop and delete test files in objectstore
1516

1617
# License
1718

package-lock.json

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"author": "",
1414
"license": "Apache-2.0",
1515
"dependencies": {
16-
"axios": "^1.6.3",
16+
"axios": "^1.6.4",
1717
"cron": "^3.1.6",
1818
"croner": "^8.0.0",
1919
"minio": "^7.1.3",

setenv-tmpl.sh

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,26 @@ export JANITOR_COPIES=3
1010
# export LOGROTATE_STATUSFILE="cron.db"
1111

1212
# Required
13+
# export OBJECT_STORAGE_ENABLED="true"
1314
export OBJECT_STORAGE_END_POINT=""
1415
export OBJECT_STORAGE_ACCESS_KEY=""
1516
export OBJECT_STORAGE_BUCKET=""
16-
# Required (if not using NR Broker & Vault)
1717
export OBJECT_STORAGE_SECRET_KEY=""
1818

19-
# Required (if using NR Broker)
20-
export BROKER_JWT=""
19+
# Set BROKER_JWT to use Broker and Vault
20+
# export BROKER_JWT=""
21+
# export BROKER_URL=""
22+
# export BROKER_USER=""
2123
export BROKER_PROJECT=""
2224
export BROKER_SERVICE=""
2325
export BROKER_ENVIRONMENT=""
26+
27+
# export VAULT_CRED_PATH=""
28+
# If VAULT_CRED_KEYS_* is set, the value from VAULT_CRED_PATH replaces OBJECT_STORAGE_*
29+
# Example: VAULT_CRED_KEYS_SECRET_KEY="secret_key" would replace OBJECT_STORAGE_SECRET_KEY
30+
# with the value of the key 'secret_key' at the path VAULT_CRED_PATH in Vault.
31+
# export VAULT_CRED_KEYS_END_POINT=""
32+
# export VAULT_CRED_KEYS_ACCESS_KEY=""
33+
# export VAULT_CRED_KEYS_BUCKET=""
34+
# export VAULT_CRED_KEYS_SECRET_KEY=""
35+
# export VAULT_URL=""

src/constants.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export const JANITOR_COPIES = Number.parseInt(
1515
);
1616

1717
// Object storage - required
18+
export const OBJECT_STORAGE_ENABLED =
19+
process.env.OBJECT_STORAGE_ENABLED == 'true' ?? true;
1820
export const OBJECT_STORAGE_END_POINT =
1921
process.env.OBJECT_STORAGE_END_POINT ?? '';
2022
export const OBJECT_STORAGE_ACCESS_KEY =
@@ -38,11 +40,20 @@ export const ENV_LONG_TO_SHORT: { [key: string]: string } = {
3840
export const BROKER_PROJECT = process.env.BROKER_PROJECT ?? '';
3941
export const BROKER_SERVICE = process.env.BROKER_SERVICE ?? '';
4042
export const BROKER_ENVIRONMENT = process.env.BROKER_ENVIRONMENT ?? '';
43+
// Path to the Object storage credentials in Vault
4144
export const VAULT_CRED_PATH =
4245
process.env.VAULT_CRED_PATH ??
4346
`/apps/${ENV_LONG_TO_SHORT[BROKER_ENVIRONMENT]}/${BROKER_PROJECT}/${BROKER_SERVICE}/rotatebackup`;
44-
45-
export const VAULT_CRED_KEY = process.env.VAULT_CRED_KEY ?? 'secret_key';
47+
// If VAULT_CRED_KEYS_* is set, the value from VAULT_CRED_PATH replaces OBJECT_STORAGE_*
48+
// Example: VAULT_CRED_KEYS_SECRET_KEY="secret_key" would replace OBJECT_STORAGE_SECRET_KEY
49+
// with the value of the key 'secret_key' at the path VAULT_CRED_PATH in Vault.
50+
export const VAULT_CRED_KEYS_END_POINT =
51+
process.env.VAULT_CRED_KEYS_END_POINT ?? '';
52+
export const VAULT_CRED_KEYS_ACCESS_KEY =
53+
process.env.VAULT_CRED_KEYS_ACCESS_KEY ?? '';
54+
export const VAULT_CRED_KEYS_BUCKET = process.env.VAULT_CRED_KEYS_BUCKET ?? '';
55+
export const VAULT_CRED_KEYS_SECRET_KEY =
56+
process.env.VAULT_CRED_KEYS_SECRET_KEY ?? '';
4657
export const VAULT_URL =
4758
process.env.VAULT_URL ?? 'https://knox.io.nrs.gov.bc.ca';
4859

src/cron/backup.ts

Lines changed: 129 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,40 @@ import {
1111
BROKER_SERVICE,
1212
BROKER_USER,
1313
DB_FILE_STATUS,
14+
OBJECT_STORAGE_ACCESS_KEY,
1415
OBJECT_STORAGE_BUCKET,
16+
OBJECT_STORAGE_ENABLED,
17+
OBJECT_STORAGE_END_POINT,
1518
OBJECT_STORAGE_SECRET_KEY,
16-
VAULT_CRED_KEY,
19+
VAULT_CRED_KEYS_ACCESS_KEY,
20+
VAULT_CRED_KEYS_BUCKET,
21+
VAULT_CRED_KEYS_END_POINT,
22+
VAULT_CRED_KEYS_SECRET_KEY,
1723
VAULT_CRED_PATH,
1824
} from '../constants';
1925
import { DatabaseService } from '../services/database.service';
2026
import VaultService from '../broker/vault.service';
2127
import BrokerService from '../broker/broker.service';
2228

29+
interface LogStatus {
30+
id: number;
31+
basename: string;
32+
path: string;
33+
}
34+
35+
interface LogArtifact {
36+
id: number;
37+
checksum: string;
38+
name: string;
39+
size: number;
40+
type: string;
41+
}
42+
43+
type FileUpdateCallback = (id: number) => Promise<any>;
44+
2345
export async function backup(db: DatabaseService) {
2446
console.log('backup: start');
25-
const result = await db.query<{
47+
const result = await db.all<{
2648
id: number;
2749
basename: string;
2850
path: string;
@@ -41,67 +63,115 @@ export async function backup(db: DatabaseService) {
4163
return;
4264
}
4365

44-
if (OBJECT_STORAGE_SECRET_KEY) {
45-
await backupWithSecret(db, OBJECT_STORAGE_SECRET_KEY, result);
46-
} else {
47-
const brokerService = new BrokerService(BROKER_JWT);
48-
try {
49-
const openResponse = await brokerService.open({
50-
event: {
51-
provider: 'nr-objectstore-rotate-backup',
52-
reason: 'Cron triggered',
53-
},
54-
actions: [
55-
{
56-
action: 'backup',
57-
id: 'backup',
58-
provision: ['token/self'],
59-
service: {
60-
name: BROKER_SERVICE,
61-
project: BROKER_PROJECT,
62-
environment: BROKER_ENVIRONMENT,
63-
},
64-
},
65-
],
66-
user: {
67-
name: BROKER_USER,
68-
},
69-
});
70-
const actionToken = openResponse.actions['backup'].token;
71-
const vaultAccessToken = await brokerService.provisionToken(actionToken);
72-
const vault = new VaultService(vaultAccessToken);
73-
const objectStorageCreds = await vault.read(VAULT_CRED_PATH);
74-
const secretKey = objectStorageCreds[VAULT_CRED_KEY];
75-
vault.revokeToken();
76-
const backupFiles = await backupWithSecret(db, secretKey, result);
77-
for (const fileObj of backupFiles) {
78-
await brokerService.attachArtifact(actionToken, fileObj);
66+
const fileUpdateCb: FileUpdateCallback = (id) => {
67+
return db.updatelogStatus(id, DB_FILE_STATUS.CopiedToObjectStore);
68+
};
69+
70+
try {
71+
if (!OBJECT_STORAGE_ENABLED) {
72+
// Skip copy to object storage
73+
for (const file of result.rows) {
74+
await db.updatelogStatus(file.id, DB_FILE_STATUS.CopiedToObjectStore);
7975
}
80-
brokerService.close(true);
81-
} catch (e: any) {
82-
// Error!
83-
console.log(e);
76+
} else if (BROKER_JWT === '') {
77+
await backupUsingEnv(result.rows, fileUpdateCb);
78+
} else {
79+
await backupUsingBroker(BROKER_JWT, result.rows, fileUpdateCb);
8480
}
81+
} catch (e: any) {
82+
// Error!
83+
console.log(e);
8584
}
8685
}
8786

87+
async function backupUsingEnv(
88+
dbFileRows: LogStatus[],
89+
cb: FileUpdateCallback,
90+
): Promise<LogArtifact[]> {
91+
const backupFiles = await backupWithSecret(
92+
dbFileRows,
93+
OBJECT_STORAGE_END_POINT,
94+
OBJECT_STORAGE_ACCESS_KEY,
95+
OBJECT_STORAGE_SECRET_KEY,
96+
OBJECT_STORAGE_BUCKET,
97+
);
98+
99+
for (const file of backupFiles) {
100+
await cb(file.id);
101+
}
102+
return backupFiles;
103+
}
104+
105+
async function backupUsingBroker(
106+
brokerJwt: string,
107+
dbFileRows: LogStatus[],
108+
cb: FileUpdateCallback,
109+
): Promise<LogArtifact[]> {
110+
const brokerService = new BrokerService(brokerJwt);
111+
const openResponse = await brokerService.open({
112+
event: {
113+
provider: 'nr-objectstore-rotate-backup',
114+
reason: 'Cron triggered',
115+
},
116+
actions: [
117+
{
118+
action: 'backup',
119+
id: 'backup',
120+
provision: ['token/self'],
121+
service: {
122+
name: BROKER_SERVICE,
123+
project: BROKER_PROJECT,
124+
environment: BROKER_ENVIRONMENT,
125+
},
126+
},
127+
],
128+
user: {
129+
name: BROKER_USER,
130+
},
131+
});
132+
const actionToken = openResponse.actions['backup'].token;
133+
const vaultAccessToken = await brokerService.provisionToken(actionToken);
134+
const vault = new VaultService(vaultAccessToken);
135+
const objectStorageCreds = await vault.read(VAULT_CRED_PATH);
136+
vault.revokeToken();
137+
const backupFiles = await backupWithSecret(
138+
dbFileRows,
139+
VAULT_CRED_KEYS_END_POINT === ''
140+
? OBJECT_STORAGE_END_POINT
141+
: objectStorageCreds[VAULT_CRED_KEYS_END_POINT],
142+
VAULT_CRED_KEYS_ACCESS_KEY === ''
143+
? OBJECT_STORAGE_ACCESS_KEY
144+
: objectStorageCreds[VAULT_CRED_KEYS_ACCESS_KEY],
145+
VAULT_CRED_KEYS_BUCKET === ''
146+
? OBJECT_STORAGE_SECRET_KEY
147+
: objectStorageCreds[VAULT_CRED_KEYS_BUCKET],
148+
VAULT_CRED_KEYS_SECRET_KEY === ''
149+
? OBJECT_STORAGE_BUCKET
150+
: objectStorageCreds[VAULT_CRED_KEYS_SECRET_KEY],
151+
);
152+
153+
for (const file of backupFiles) {
154+
await brokerService.attachArtifact(actionToken, file);
155+
await cb(file.id);
156+
}
157+
brokerService.close(true);
158+
159+
return backupFiles;
160+
}
161+
88162
async function backupWithSecret(
89-
db: DatabaseService,
90-
secret: string,
91-
dbResult: {
92-
rows: {
93-
id: number;
94-
basename: string;
95-
path: string;
96-
}[];
97-
},
98-
): Promise<any[]> {
99-
const client = getClient(secret);
100-
const files = [];
101-
for (const row of dbResult.rows) {
163+
dbFileRows: LogStatus[],
164+
endPoint: string,
165+
accessKey: string,
166+
secretKey: string,
167+
bucket: string,
168+
): Promise<LogArtifact[]> {
169+
const client = getClient(endPoint, accessKey, secretKey);
170+
const files: LogArtifact[] = [];
171+
for (const row of dbFileRows) {
102172
try {
103173
const response = await client.fPutObject(
104-
OBJECT_STORAGE_BUCKET,
174+
bucket,
105175
path.basename(row.path),
106176
row.path,
107177
);
@@ -111,24 +181,15 @@ async function backupWithSecret(
111181
console.log(info);
112182
continue;
113183
}
114-
db.query<{
115-
id: number;
116-
basename: string;
117-
path: string;
118-
}>(
119-
`
120-
UPDATE logs
121-
SET status = ?
122-
WHERE id = ?
123-
`,
124-
[DB_FILE_STATUS.CopiedToObjectStore, row.id],
125-
);
184+
const checksum = `sha256:${await computeHash(row.path)}`;
126185
files.push({
127-
checksum: `sha256:${await computeHash(row.path)}`,
186+
id: row.id,
187+
checksum,
128188
name: path.basename(row.path),
129189
size: fs.statSync(row.path).size,
130190
type: 'tgz',
131191
});
192+
console.log(`backup: Sent ${row.path} [${checksum}]`);
132193
}
133194
return files;
134195
}

0 commit comments

Comments
 (0)