@serve.zone/gitops 32.1.0 → 32.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +18 -0
- package/deno.json +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes/actionlog.d.ts +1 -1
- package/dist_ts/classes/actionlog.js +1 -1
- package/dist_ts/classes/connectionmanager.d.ts +1 -5
- package/dist_ts/classes/connectionmanager.js +5 -28
- package/dist_ts/classes/gitopsapp.d.ts +8 -3
- package/dist_ts/classes/gitopsapp.js +29 -18
- package/dist_ts/{cache/classes.secrets.scan.service.d.ts → classes/secrets.scan.service.d.ts} +1 -1
- package/dist_ts/{cache/classes.secrets.scan.service.js → classes/secrets.scan.service.js} +3 -3
- package/dist_ts/{cache → db}/classes.cache.cleaner.d.ts +1 -3
- package/dist_ts/db/classes.cache.cleaner.js +59 -0
- package/dist_ts/{cache → db}/classes.cached.document.d.ts +1 -1
- package/dist_ts/{cache → db}/classes.cached.document.js +2 -2
- package/dist_ts/db/classes.gitops-db.d.ts +34 -0
- package/dist_ts/db/classes.gitops-db.js +77 -0
- package/dist_ts/{cache → db}/documents/classes.cached.project.js +3 -3
- package/dist_ts/{cache → db}/documents/classes.cached.secret.js +3 -3
- package/dist_ts/db/documents/classes.storage.record.d.ts +14 -0
- package/dist_ts/db/documents/classes.storage.record.js +82 -0
- package/dist_ts/{cache → db}/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +4 -0
- package/dist_ts/db/index.d.ts +5 -0
- package/dist_ts/db/index.js +5 -0
- package/dist_ts/paths.d.ts +2 -2
- package/dist_ts/paths.js +2 -3
- package/dist_ts/plugins.d.ts +2 -3
- package/dist_ts/plugins.js +6 -4
- package/dist_ts/storage/classes.storagemanager.d.ts +7 -16
- package/dist_ts/storage/classes.storagemanager.js +54 -94
- package/dist_ts/storage/index.d.ts +0 -1
- package/dist_ts_migrations/classes.migration-record.d.ts +11 -0
- package/dist_ts_migrations/classes.migration-record.js +80 -0
- package/dist_ts_migrations/import-filesystem-records.d.ts +9 -0
- package/dist_ts_migrations/import-filesystem-records.js +74 -0
- package/dist_ts_migrations/index.d.ts +12 -0
- package/dist_ts_migrations/index.js +24 -0
- package/dist_ts_migrations/interfaces.d.ts +16 -0
- package/dist_ts_migrations/interfaces.js +2 -0
- package/package.json +6 -4
- package/readme.md +21 -14
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes/actionlog.ts +1 -1
- package/ts/classes/connectionmanager.ts +4 -27
- package/ts/classes/gitopsapp.ts +36 -17
- package/ts/{cache/classes.secrets.scan.service.ts → classes/secrets.scan.service.ts} +3 -3
- package/ts/{cache → db}/classes.cache.cleaner.ts +1 -4
- package/ts/{cache → db}/classes.cached.document.ts +1 -1
- package/ts/db/classes.gitops-db.ts +92 -0
- package/ts/{cache → db}/documents/classes.cached.project.ts +2 -2
- package/ts/{cache → db}/documents/classes.cached.secret.ts +2 -2
- package/ts/db/documents/classes.storage.record.ts +26 -0
- package/ts/{cache → db}/documents/index.ts +1 -0
- package/ts/db/index.ts +5 -0
- package/ts/paths.ts +3 -4
- package/ts/plugins.ts +5 -3
- package/ts/storage/classes.storagemanager.ts +59 -95
- package/ts/storage/index.ts +0 -1
- package/ts_migrations/classes.migration-record.ts +23 -0
- package/ts_migrations/import-filesystem-records.ts +79 -0
- package/ts_migrations/index.ts +31 -0
- package/ts_migrations/interfaces.ts +18 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/dist_ts/cache/classes.cache.cleaner.js +0 -61
- package/dist_ts/cache/classes.cachedb.d.ts +0 -22
- package/dist_ts/cache/classes.cachedb.js +0 -58
- package/dist_ts/cache/documents/index.js +0 -3
- package/dist_ts/cache/index.d.ts +0 -7
- package/dist_ts/cache/index.js +0 -6
- package/ts/cache/classes.cachedb.ts +0 -72
- package/ts/cache/index.ts +0 -7
- /package/dist_ts/{cache → db}/documents/classes.cached.project.d.ts +0 -0
- /package/dist_ts/{cache → db}/documents/classes.cached.secret.d.ts +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import * as plugins from '../dist_ts/plugins.js';
|
|
36
|
+
import { GitopsDb } from '../dist_ts/db/index.js';
|
|
37
|
+
/**
|
|
38
|
+
* The ledger of migrations that have run to completion.
|
|
39
|
+
*
|
|
40
|
+
* `migration` owns the stored `_id`, so a migration is looked up by primary key and can
|
|
41
|
+
* only ever be recorded once.
|
|
42
|
+
*/
|
|
43
|
+
let MigrationRecord = (() => {
|
|
44
|
+
let _classDecorators = [plugins.smartdata.Collection(() => GitopsDb.getInstance().getDb(), {
|
|
45
|
+
identityAsDocumentId: 'migration',
|
|
46
|
+
})];
|
|
47
|
+
let _classDescriptor;
|
|
48
|
+
let _classExtraInitializers = [];
|
|
49
|
+
let _classThis;
|
|
50
|
+
let _classSuper = plugins.smartdata.SmartDataDbDoc;
|
|
51
|
+
let _migration_decorators;
|
|
52
|
+
let _migration_initializers = [];
|
|
53
|
+
let _migration_extraInitializers = [];
|
|
54
|
+
let _completedAt_decorators;
|
|
55
|
+
let _completedAt_initializers = [];
|
|
56
|
+
let _completedAt_extraInitializers = [];
|
|
57
|
+
var MigrationRecord = class extends _classSuper {
|
|
58
|
+
static { _classThis = this; }
|
|
59
|
+
static {
|
|
60
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
61
|
+
_migration_decorators = [plugins.smartdata.unI(), plugins.smartdata.svDb()];
|
|
62
|
+
_completedAt_decorators = [plugins.smartdata.svDb()];
|
|
63
|
+
__esDecorate(null, null, _migration_decorators, { kind: "field", name: "migration", static: false, private: false, access: { has: obj => "migration" in obj, get: obj => obj.migration, set: (obj, value) => { obj.migration = value; } }, metadata: _metadata }, _migration_initializers, _migration_extraInitializers);
|
|
64
|
+
__esDecorate(null, null, _completedAt_decorators, { kind: "field", name: "completedAt", static: false, private: false, access: { has: obj => "completedAt" in obj, get: obj => obj.completedAt, set: (obj, value) => { obj.completedAt = value; } }, metadata: _metadata }, _completedAt_initializers, _completedAt_extraInitializers);
|
|
65
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
66
|
+
MigrationRecord = _classThis = _classDescriptor.value;
|
|
67
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
68
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
69
|
+
}
|
|
70
|
+
migration = __runInitializers(this, _migration_initializers, void 0);
|
|
71
|
+
completedAt = (__runInitializers(this, _migration_extraInitializers), __runInitializers(this, _completedAt_initializers, void 0));
|
|
72
|
+
constructor() {
|
|
73
|
+
super(...arguments);
|
|
74
|
+
__runInitializers(this, _completedAt_extraInitializers);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return MigrationRecord = _classThis;
|
|
78
|
+
})();
|
|
79
|
+
export { MigrationRecord };
|
|
80
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5taWdyYXRpb24tcmVjb3JkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfbWlncmF0aW9ucy9jbGFzc2VzLm1pZ3JhdGlvbi1yZWNvcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRTdDOzs7OztHQUtHO0lBSVUsZUFBZTs0QkFIM0IsT0FBTyxDQUFDLFNBQVMsQ0FBQyxVQUFVLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLFdBQVcsRUFBRSxDQUFDLEtBQUssRUFBRSxFQUFFO1lBQ2xFLG9CQUFvQixFQUFFLFdBQVc7U0FDbEMsQ0FBQzs7OztzQkFDbUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxjQUFjOzs7Ozs7OytCQUF4QyxTQUFRLFdBR3BDOzs7O3FDQUNFLE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFLEVBQ3ZCLE9BQU8sQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFO3VDQUd4QixPQUFPLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRTtZQUZ6Qiw2S0FBTyxTQUFTLDZCQUFULFNBQVMsNkZBQVU7WUFHMUIsbUxBQU8sV0FBVyw2QkFBWCxXQUFXLGlHQUFVO1lBVDlCLDZLQVVDOzs7WUFWWSx1REFBZTs7UUFNbkIsU0FBUyw0REFBVTtRQUduQixXQUFXLHVIQUFVOzs7Ozs7OztTQVRqQixlQUFlIn0=
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IMigration } from './interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* Imports the key/value records that earlier releases kept as files.
|
|
4
|
+
*
|
|
5
|
+
* Both sources are read once and never written back: the ledger, not a renamed file, is
|
|
6
|
+
* what records that this ran. The legacy tree is left on disk untouched so an operator can
|
|
7
|
+
* archive it after confirming the import.
|
|
8
|
+
*/
|
|
9
|
+
export declare const importFilesystemRecords: IMigration;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as plugins from '../dist_ts/plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reads every regular file below `directoryArg` and yields it as a storage key relative to
|
|
4
|
+
* the tree root, so `<root>/connections/a.json` becomes `/connections/a.json`.
|
|
5
|
+
*/
|
|
6
|
+
const readDirectoryEntries = async (directoryArg) => {
|
|
7
|
+
try {
|
|
8
|
+
return await plugins.fs.readdir(directoryArg, { withFileTypes: true });
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code === 'ENOENT')
|
|
12
|
+
return [];
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const readLegacyTree = async (directoryArg, prefixArg) => {
|
|
17
|
+
const entries = await readDirectoryEntries(directoryArg);
|
|
18
|
+
const records = [];
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const entryPath = plugins.path.join(directoryArg, entry.name);
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
records.push(...(await readLegacyTree(entryPath, `${prefixArg}/${entry.name}`)));
|
|
23
|
+
}
|
|
24
|
+
else if (entry.isFile()) {
|
|
25
|
+
records.push({
|
|
26
|
+
key: `${prefixArg}/${entry.name}`,
|
|
27
|
+
value: await plugins.fs.readFile(entryPath, 'utf8'),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return records;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Imports the key/value records that earlier releases kept as files.
|
|
35
|
+
*
|
|
36
|
+
* Both sources are read once and never written back: the ledger, not a renamed file, is
|
|
37
|
+
* what records that this ran. The legacy tree is left on disk untouched so an operator can
|
|
38
|
+
* archive it after confirming the import.
|
|
39
|
+
*/
|
|
40
|
+
export const importFilesystemRecords = {
|
|
41
|
+
name: 'import-filesystem-records',
|
|
42
|
+
run: async (contextArg) => {
|
|
43
|
+
// The development-time connections file was the authority over the tree before, and
|
|
44
|
+
// stays it here: it is written first and the tree import below skips existing keys.
|
|
45
|
+
try {
|
|
46
|
+
const legacyText = await plugins.fs.readFile(contextArg.legacyConnectionsFile, 'utf8');
|
|
47
|
+
const legacyConnections = JSON.parse(legacyText);
|
|
48
|
+
for (const connection of legacyConnections) {
|
|
49
|
+
await contextArg.storage.setJSON(`/connections/${connection.id}.json`, connection);
|
|
50
|
+
}
|
|
51
|
+
if (legacyConnections.length > 0) {
|
|
52
|
+
contextArg.log(`imported ${legacyConnections.length} connection(s) from the legacy file`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error.code !== 'ENOENT')
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
const records = await readLegacyTree(contextArg.legacyStoragePath, '');
|
|
60
|
+
let imported = 0;
|
|
61
|
+
for (const record of records) {
|
|
62
|
+
// A key that already exists was written by a newer authority — the legacy file above
|
|
63
|
+
// or a release that already ran against the database — so it is never overwritten.
|
|
64
|
+
if (await contextArg.storage.exists(record.key))
|
|
65
|
+
continue;
|
|
66
|
+
await contextArg.storage.set(record.key, record.value);
|
|
67
|
+
imported++;
|
|
68
|
+
}
|
|
69
|
+
if (imported > 0) {
|
|
70
|
+
contextArg.log(`imported ${imported} record(s) from ${contextArg.legacyStoragePath}`);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0LWZpbGVzeXN0ZW0tcmVjb3Jkcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX21pZ3JhdGlvbnMvaW1wb3J0LWZpbGVzeXN0ZW0tcmVjb3Jkcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBTzVDOzs7R0FHRztBQUNILE1BQU0sb0JBQW9CLEdBQUcsS0FBSyxFQUFFLFlBQW9CLEVBQUUsRUFBRTtJQUMxRCxJQUFJLENBQUM7UUFDSCxPQUFPLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsWUFBWSxFQUFFLEVBQUUsYUFBYSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7SUFDekUsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixJQUFLLEtBQStCLENBQUMsSUFBSSxLQUFLLFFBQVE7WUFBRSxPQUFPLEVBQUUsQ0FBQztRQUNsRSxNQUFNLEtBQUssQ0FBQztJQUNkLENBQUM7QUFDSCxDQUFDLENBQUM7QUFFRixNQUFNLGNBQWMsR0FBRyxLQUFLLEVBQzFCLFlBQW9CLEVBQ3BCLFNBQWlCLEVBQzBCLEVBQUU7SUFDN0MsTUFBTSxPQUFPLEdBQUcsTUFBTSxvQkFBb0IsQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUN6RCxNQUFNLE9BQU8sR0FBcUMsRUFBRSxDQUFDO0lBQ3JELEtBQUssTUFBTSxLQUFLLElBQUksT0FBTyxFQUFFLENBQUM7UUFDNUIsTUFBTSxTQUFTLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM5RCxJQUFJLEtBQUssQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDO1lBQ3hCLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxHQUFHLFNBQVMsSUFBSSxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDbkYsQ0FBQzthQUFNLElBQUksS0FBSyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUM7WUFDMUIsT0FBTyxDQUFDLElBQUksQ0FBQztnQkFDWCxHQUFHLEVBQUUsR0FBRyxTQUFTLElBQUksS0FBSyxDQUFDLElBQUksRUFBRTtnQkFDakMsS0FBSyxFQUFFLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQzthQUNwRCxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztJQUNELE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUMsQ0FBQztBQUVGOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFlO0lBQ2pELElBQUksRUFBRSwyQkFBMkI7SUFDakMsR0FBRyxFQUFFLEtBQUssRUFBRSxVQUE2QixFQUFpQixFQUFFO1FBQzFELG9GQUFvRjtRQUNwRixvRkFBb0Y7UUFDcEYsSUFBSSxDQUFDO1lBQ0gsTUFBTSxVQUFVLEdBQUcsTUFBTSxPQUFPLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMscUJBQXFCLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDdkYsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBd0IsQ0FBQztZQUN4RSxLQUFLLE1BQU0sVUFBVSxJQUFJLGlCQUFpQixFQUFFLENBQUM7Z0JBQzNDLE1BQU0sVUFBVSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLFVBQVUsQ0FBQyxFQUFFLE9BQU8sRUFBRSxVQUFVLENBQUMsQ0FBQztZQUNyRixDQUFDO1lBQ0QsSUFBSSxpQkFBaUIsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQ2pDLFVBQVUsQ0FBQyxHQUFHLENBQUMsWUFBWSxpQkFBaUIsQ0FBQyxNQUFNLHFDQUFxQyxDQUFDLENBQUM7WUFDNUYsQ0FBQztRQUNILENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSyxLQUErQixDQUFDLElBQUksS0FBSyxRQUFRO2dCQUFFLE1BQU0sS0FBSyxDQUFDO1FBQ3RFLENBQUM7UUFFRCxNQUFNLE9BQU8sR0FBRyxNQUFNLGNBQWMsQ0FBQyxVQUFVLENBQUMsaUJBQWlCLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDdkUsSUFBSSxRQUFRLEdBQUcsQ0FBQyxDQUFDO1FBQ2pCLEtBQUssTUFBTSxNQUFNLElBQUksT0FBTyxFQUFFLENBQUM7WUFDN0IscUZBQXFGO1lBQ3JGLG1GQUFtRjtZQUNuRixJQUFJLE1BQU0sVUFBVSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQztnQkFBRSxTQUFTO1lBQzFELE1BQU0sVUFBVSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsRUFBRSxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDdkQsUUFBUSxFQUFFLENBQUM7UUFDYixDQUFDO1FBQ0QsSUFBSSxRQUFRLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDakIsVUFBVSxDQUFDLEdBQUcsQ0FBQyxZQUFZLFFBQVEsbUJBQW1CLFVBQVUsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDLENBQUM7UUFDeEYsQ0FBQztJQUNILENBQUM7Q0FDRixDQUFDIn0=
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IMigrationContext } from './interfaces.js';
|
|
2
|
+
export type { IMigration, IMigrationContext } from './interfaces.js';
|
|
3
|
+
export { MigrationRecord } from './classes.migration-record.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs the pending migrations against an already connected database.
|
|
6
|
+
*
|
|
7
|
+
* Called from startup only, after the database is up and before any manager reads a
|
|
8
|
+
* record. No lease is taken: every migration is idempotent, so two instances starting at
|
|
9
|
+
* the same time converge on the same result, while a lease would let one crashed claimant
|
|
10
|
+
* block every later start.
|
|
11
|
+
*/
|
|
12
|
+
export declare const runStartupMigrations: (contextArg: IMigrationContext) => Promise<void>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MigrationRecord } from './classes.migration-record.js';
|
|
2
|
+
import { importFilesystemRecords } from './import-filesystem-records.js';
|
|
3
|
+
export { MigrationRecord } from './classes.migration-record.js';
|
|
4
|
+
/** Applied in order; a name that is already in the ledger is skipped. */
|
|
5
|
+
const migrations = [importFilesystemRecords];
|
|
6
|
+
/**
|
|
7
|
+
* Runs the pending migrations against an already connected database.
|
|
8
|
+
*
|
|
9
|
+
* Called from startup only, after the database is up and before any manager reads a
|
|
10
|
+
* record. No lease is taken: every migration is idempotent, so two instances starting at
|
|
11
|
+
* the same time converge on the same result, while a lease would let one crashed claimant
|
|
12
|
+
* block every later start.
|
|
13
|
+
*/
|
|
14
|
+
export const runStartupMigrations = async (contextArg) => {
|
|
15
|
+
for (const migration of migrations) {
|
|
16
|
+
if (await MigrationRecord.exists({ migration: migration.name }))
|
|
17
|
+
continue;
|
|
18
|
+
contextArg.log(`running migration ${migration.name}`);
|
|
19
|
+
await migration.run(contextArg);
|
|
20
|
+
await MigrationRecord.atomicUpdate({ migration: migration.name }, { $set: { completedAt: Date.now() } }, { upsert: true });
|
|
21
|
+
contextArg.log(`migration ${migration.name} completed`);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19taWdyYXRpb25zL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUNoRSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUl6RSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFFaEUseUVBQXlFO0FBQ3pFLE1BQU0sVUFBVSxHQUEwQixDQUFDLHVCQUF1QixDQUFDLENBQUM7QUFFcEU7Ozs7Ozs7R0FPRztBQUNILE1BQU0sQ0FBQyxNQUFNLG9CQUFvQixHQUFHLEtBQUssRUFBRSxVQUE2QixFQUFpQixFQUFFO0lBQ3pGLEtBQUssTUFBTSxTQUFTLElBQUksVUFBVSxFQUFFLENBQUM7UUFDbkMsSUFBSSxNQUFNLGVBQWUsQ0FBQyxNQUFNLENBQWtCLEVBQUUsU0FBUyxFQUFFLFNBQVMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUFFLFNBQVM7UUFDM0YsVUFBVSxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsU0FBUyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7UUFDdEQsTUFBTSxTQUFTLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ2hDLE1BQU0sZUFBZSxDQUFDLFlBQVksQ0FDaEMsRUFBRSxTQUFTLEVBQUUsU0FBUyxDQUFDLElBQUksRUFBRSxFQUM3QixFQUFFLElBQUksRUFBRSxFQUFFLFdBQVcsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUUsRUFBRSxFQUNyQyxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsQ0FDakIsQ0FBQztRQUNGLFVBQVUsQ0FBQyxHQUFHLENBQUMsYUFBYSxTQUFTLENBQUMsSUFBSSxZQUFZLENBQUMsQ0FBQztJQUMxRCxDQUFDO0FBQ0gsQ0FBQyxDQUFDIn0=
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StorageManager } from '../dist_ts/storage/index.js';
|
|
2
|
+
export interface IMigrationContext {
|
|
3
|
+
/** The document-backed key/value store every migration writes through. */
|
|
4
|
+
storage: StorageManager;
|
|
5
|
+
/** Root of the key/value tree the releases before the document database wrote. */
|
|
6
|
+
legacyStoragePath: string;
|
|
7
|
+
/** The development-time connection list earlier releases imported on every start. */
|
|
8
|
+
legacyConnectionsFile: string;
|
|
9
|
+
/** Reports progress to the application log. */
|
|
10
|
+
log: (messageArg: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface IMigration {
|
|
13
|
+
/** Stable identity recorded in the ledger once the migration completed. */
|
|
14
|
+
name: string;
|
|
15
|
+
run: (contextArg: IMigrationContext) => Promise<void>;
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/gitops",
|
|
3
|
-
"version": "32.
|
|
3
|
+
"version": "32.2.0",
|
|
4
4
|
"description": "GitOps management app for Gitea and GitLab - manage secrets, browse projects, view CI pipelines, and stream build logs",
|
|
5
5
|
"main": "./dist_ts/index.js",
|
|
6
6
|
"typings": "./dist_ts/index.d.ts",
|
|
@@ -31,10 +31,9 @@
|
|
|
31
31
|
"@apiclient.xyz/gitlab": "^2.6.0",
|
|
32
32
|
"@design.estate/dees-catalog": "3.83.0",
|
|
33
33
|
"@design.estate/dees-element": "^2.2.4",
|
|
34
|
-
"@
|
|
34
|
+
"@lossless.org/client": "1.1.0",
|
|
35
35
|
"@push.rocks/smartguard": "^3.1.0",
|
|
36
36
|
"@push.rocks/smartjwt": "^2.2.2",
|
|
37
|
-
"@push.rocks/smartmongo": "9.0.0",
|
|
38
37
|
"@push.rocks/smartsecret": "^1.0.2"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
@@ -45,16 +44,19 @@
|
|
|
45
44
|
"@git.zone/tsrun": "^2.0.6",
|
|
46
45
|
"@git.zone/tstest": "6.1.1",
|
|
47
46
|
"@git.zone/tswatch": "^3.3.5",
|
|
47
|
+
"@push.rocks/smartmongo": "9.0.0",
|
|
48
48
|
"@types/node": "^26.5.1"
|
|
49
49
|
},
|
|
50
50
|
"files": [
|
|
51
51
|
"ts/**/*",
|
|
52
52
|
"binary/**/*",
|
|
53
53
|
"ts_interfaces/**/*",
|
|
54
|
+
"ts_migrations/**/*",
|
|
54
55
|
"ts_web/**/*",
|
|
55
56
|
"dist_*/**/*",
|
|
56
57
|
"dist_ts/**/*",
|
|
57
58
|
"dist_ts_interfaces/**/*",
|
|
59
|
+
"dist_ts_migrations/**/*",
|
|
58
60
|
"dist_ts_web/**/*",
|
|
59
61
|
"dist_serve/**/*",
|
|
60
62
|
"cli.js",
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
"start": "(node ./cli.js)",
|
|
72
74
|
"startTs": "(node ./cli.ts.js)",
|
|
73
75
|
"test": "(pnpm run build && tstest \"test/*.node.ts\" --verbose --logfile --timeout 60)",
|
|
74
|
-
"build": "(tsbuild custom ts_interfaces ts && pnpm run bundle)",
|
|
76
|
+
"build": "(tsbuild custom ts_interfaces ts_migrations ts && pnpm run bundle)",
|
|
75
77
|
"build:binary": "(pnpm run build && tsdeno compile)",
|
|
76
78
|
"build:docker": "tsdocker build --verbose",
|
|
77
79
|
"release:docker": "tsdocker push --verbose",
|
package/readme.md
CHANGED
|
@@ -11,7 +11,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
11
11
|
- Connects to Gitea and GitLab through provider adapters with a shared project, group, secret, pipeline, and file-content interface.
|
|
12
12
|
- Stores provider tokens and BookStack job tokens through `@push.rocks/smartsecret`, using OS keychain support where available and an encrypted fallback otherwise.
|
|
13
13
|
- Serves a bundled Lit/dees-catalog single page app through `@api.global/typedserver` with TypedRequest handlers for all backend actions.
|
|
14
|
-
- Scans project and group CI/CD secrets into
|
|
14
|
+
- Scans project and group CI/CD secrets into the document database and refreshes that cache on startup and every 24 hours.
|
|
15
15
|
- Mirrors repositories from a source connection to a target connection with group offset support, stable source-project mapping for subgroup/path shifts, metadata sync, optional release/asset and OCI image sync, stale target handling, and live sync logs.
|
|
16
16
|
- Manages reusable secret definitions that can be pushed to selected project or group targets.
|
|
17
17
|
- Runs scheduled jobs, currently focused on syncing `readme.md`, `changelog.md`, and `docs/*.md` content into BookStack shelves, books, and pages.
|
|
@@ -28,24 +28,26 @@ GitOps is a Node.js service packaged as an npm module. Deno is used only for com
|
|
|
28
28
|
| Web server | `ts/opsserver/`, TypedServer plus TypedRequest handlers |
|
|
29
29
|
| Shared contracts | `ts_interfaces/` data and request interfaces |
|
|
30
30
|
| Frontend | `ts_web/`, Lit web components and smartstate |
|
|
31
|
-
| Build output | `dist_ts/`, `dist_ts_interfaces/`, and `dist_serve/` generated by `tsbuild` and `tsbundle` |
|
|
32
|
-
|
|
|
33
|
-
|
|
|
31
|
+
| Build output | `dist_ts/`, `dist_ts_interfaces/`, `dist_ts_migrations/`, and `dist_serve/` generated by `tsbuild` and `tsbundle` |
|
|
32
|
+
| Startup migrations | `ts_migrations/`, applied once against the connected database |
|
|
33
|
+
| Persistence | One document database, reached through `@lossless.org/client/nosqldb` |
|
|
34
34
|
|
|
35
35
|
## Installation and Usage
|
|
36
36
|
|
|
37
|
+
gitops keeps every durable record in one document database, so a MongoDB-compatible server must be reachable before the first start and its URL must be present in `GITOPS_MONGODB_URL`; without it the process refuses by name and exits. An installation upgrading from a release that kept its records under `~/.serve.zone/gitops/storage/` keeps that directory mounted for the first start, which imports the records once.
|
|
38
|
+
|
|
37
39
|
Install the package through pnpm and start the public `gitops` binary:
|
|
38
40
|
|
|
39
41
|
```bash
|
|
40
42
|
pnpm add -g @serve.zone/gitops
|
|
41
|
-
GITOPS_PORT=3000 gitops server
|
|
43
|
+
GITOPS_MONGODB_URL=mongodb://localhost:27017 GITOPS_PORT=3000 gitops server
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
For local source runs, use the checked-in Node entrypoint:
|
|
45
47
|
|
|
46
48
|
```bash
|
|
47
|
-
node ./cli.js server
|
|
48
|
-
GITOPS_PORT=3001 node ./cli.js server
|
|
49
|
+
GITOPS_MONGODB_URL=mongodb://localhost:27017 node ./cli.js server
|
|
50
|
+
GITOPS_MONGODB_URL=mongodb://localhost:27017 GITOPS_PORT=3001 node ./cli.js server
|
|
49
51
|
```
|
|
50
52
|
|
|
51
53
|
Container image sync shells out to `skopeo copy --all`; source runs need `skopeo` on `PATH`. The packaged Docker image installs `skopeo` for this path.
|
|
@@ -61,10 +63,10 @@ import type * as gitopsInterfaces from '@serve.zone/gitops/interfaces';
|
|
|
61
63
|
|
|
62
64
|
| Component | Role |
|
|
63
65
|
| --- | --- |
|
|
64
|
-
| `GitopsApp` |
|
|
66
|
+
| `GitopsApp` | Connects the database, applies pending migrations, then starts provider connections, sync, jobs, managed secrets, scans, and the OpsServer. |
|
|
65
67
|
| `ConnectionManager` | Persists Gitea/GitLab connections, migrates old plaintext tokens into SmartSecret, and background-checks connection health. |
|
|
66
68
|
| `GiteaProvider` / `GitLabProvider` | Provider-specific adapters behind a shared `BaseProvider` interface. |
|
|
67
|
-
| `SecretsScanService` | Fetches project and group secrets in batches and upserts them into
|
|
69
|
+
| `SecretsScanService` | Fetches project and group secrets in batches and upserts them into `CachedSecret` documents. |
|
|
68
70
|
| `SyncManager` | Mirrors repositories with bare git clones, stable source/target path mapping, deletion safeguards, branch/default-branch handling, metadata sync, release/asset sync, and OCI image sync. |
|
|
69
71
|
| `ManagedSecretsManager` | Stores define-once secret records and pushes them to selected project or group targets. |
|
|
70
72
|
| `JobManager` | Stores scheduled jobs, starts interval timers, masks persisted secrets, and dispatches job execution to registered runners. |
|
|
@@ -74,11 +76,15 @@ import type * as gitopsInterfaces from '@serve.zone/gitops/interfaces';
|
|
|
74
76
|
|
|
75
77
|
| Variable | Default | Purpose |
|
|
76
78
|
| --- | --- | --- |
|
|
79
|
+
| `GITOPS_MONGODB_URL` | none, required | Connection URL of the document database. Startup fails without it. |
|
|
80
|
+
| `GITOPS_MONGODB_NAME` | `gitops` | Database name inside that server. |
|
|
77
81
|
| `GITOPS_PORT` | `3000` | HTTP and WebSocket server port. |
|
|
78
82
|
| `GITOPS_ADMIN_USERNAME` | `admin` | Dashboard login username. |
|
|
79
83
|
| `GITOPS_ADMIN_PASSWORD` | `admin` | Dashboard login password. |
|
|
80
84
|
|
|
81
|
-
Provider connections, sync configs, jobs, managed secrets, and action logs are persisted as
|
|
85
|
+
Provider connections, sync configs, jobs, managed secrets, and action logs are persisted as `StorageRecord` documents, keyed by the same `/connections/<id>.json`-style keys earlier releases used as file names. Cached projects and secrets are `CachedProject` and `CachedSecret` documents in the same database, bounded by their TTL and swept hourly by the cache cleaner.
|
|
86
|
+
|
|
87
|
+
A release that finds records below `~/.serve.zone/gitops/storage/` imports them once, on the first start against the database, and records that in the migration ledger. The directory is read, never written, and is left in place so it can be archived after the import is confirmed.
|
|
82
88
|
|
|
83
89
|
Provider connections may also define `registryUrl`, `registryUsername`, and `registryToken` for OCI registry mirroring. Registry tokens are stored through SmartSecret and masked in API/UI responses. GitLab image sync falls back to `oauth2:<provider token>` when no dedicated registry credentials are configured; Gitea requires an explicit registry username when registry auth is needed.
|
|
84
90
|
|
|
@@ -183,11 +189,11 @@ pnpm test
|
|
|
183
189
|
pnpm run startTs
|
|
184
190
|
```
|
|
185
191
|
|
|
186
|
-
Useful direct commands:
|
|
192
|
+
Useful direct commands (each server start needs `GITOPS_MONGODB_URL`):
|
|
187
193
|
|
|
188
194
|
```bash
|
|
189
|
-
node ./cli.js server
|
|
190
|
-
GITOPS_PORT=3001 node ./cli.js server
|
|
195
|
+
GITOPS_MONGODB_URL=mongodb://localhost:27017 node ./cli.js server
|
|
196
|
+
GITOPS_MONGODB_URL=mongodb://localhost:27017 GITOPS_PORT=3001 node ./cli.js server
|
|
191
197
|
pnpm run build:binary
|
|
192
198
|
pnpm run build:docker
|
|
193
199
|
pnpm run watch
|
|
@@ -206,11 +212,12 @@ gitops/
|
|
|
206
212
|
├── html/
|
|
207
213
|
├── ts/
|
|
208
214
|
│ ├── classes/
|
|
209
|
-
│ ├──
|
|
215
|
+
│ ├── db/
|
|
210
216
|
│ ├── opsserver/
|
|
211
217
|
│ ├── providers/
|
|
212
218
|
│ └── storage/
|
|
213
219
|
├── ts_interfaces/
|
|
220
|
+
├── ts_migrations/
|
|
214
221
|
├── ts_web/
|
|
215
222
|
├── dist_serve/
|
|
216
223
|
└── test/
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/classes/actionlog.ts
CHANGED
|
@@ -6,7 +6,7 @@ const ACTIONLOG_PREFIX = '/actionlog/';
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Persists and queries action log entries via StorageManager.
|
|
9
|
-
* Entries are stored as individual
|
|
9
|
+
* Entries are stored as individual records keyed by timestamp-id.
|
|
10
10
|
*/
|
|
11
11
|
export class ActionLog {
|
|
12
12
|
private storageManager: StorageManager;
|
|
@@ -4,7 +4,6 @@ import type * as interfaces from '../../ts_interfaces/index.js';
|
|
|
4
4
|
import { BaseProvider, GiteaProvider, GitLabProvider } from '../providers/index.js';
|
|
5
5
|
import type { StorageManager } from '../storage/index.js';
|
|
6
6
|
|
|
7
|
-
const LEGACY_CONNECTIONS_FILE = './.nogit/connections.json';
|
|
8
7
|
const CONNECTIONS_PREFIX = '/connections/';
|
|
9
8
|
const KEYCHAIN_PREFIX = 'keychain:';
|
|
10
9
|
|
|
@@ -19,7 +18,7 @@ const mintWebhookSecret = (): string => plugins.crypto.randomBytes(32).toString(
|
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Manages provider connections — persists each connection as an
|
|
22
|
-
* individual
|
|
21
|
+
* individual record via StorageManager. Tokens are stored in
|
|
23
22
|
* the OS keychain (or encrypted file fallback) via SmartSecret.
|
|
24
23
|
*/
|
|
25
24
|
export class ConnectionManager {
|
|
@@ -35,7 +34,6 @@ export class ConnectionManager {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
async init(): Promise<void> {
|
|
38
|
-
await this.migrateLegacyFile();
|
|
39
37
|
await this.loadConnections();
|
|
40
38
|
// Auto-test all connections in the background
|
|
41
39
|
this.healthCheckDone = this.testAllConnections();
|
|
@@ -59,27 +57,6 @@ export class ConnectionManager {
|
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
/**
|
|
63
|
-
* One-time migration from the legacy .nogit/connections.json file.
|
|
64
|
-
*/
|
|
65
|
-
private async migrateLegacyFile(): Promise<void> {
|
|
66
|
-
try {
|
|
67
|
-
const text = await plugins.fs.readFile(LEGACY_CONNECTIONS_FILE, 'utf8');
|
|
68
|
-
const legacy: interfaces.data.IProviderConnection[] = JSON.parse(text);
|
|
69
|
-
if (legacy.length > 0) {
|
|
70
|
-
logger.info(`Migrating ${legacy.length} connection(s) from legacy file...`);
|
|
71
|
-
for (const conn of legacy) {
|
|
72
|
-
await this.storageManager.setJSON(`${CONNECTIONS_PREFIX}${conn.id}.json`, conn);
|
|
73
|
-
}
|
|
74
|
-
// Rename legacy file so migration doesn't repeat
|
|
75
|
-
await plugins.fs.rename(LEGACY_CONNECTIONS_FILE, LEGACY_CONNECTIONS_FILE + '.migrated');
|
|
76
|
-
logger.success('Legacy connections migrated successfully');
|
|
77
|
-
}
|
|
78
|
-
} catch {
|
|
79
|
-
// No legacy file or already migrated — nothing to do
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
60
|
private async loadConnections(): Promise<void> {
|
|
84
61
|
const keys = await this.storageManager.list(CONNECTIONS_PREFIX);
|
|
85
62
|
this.connections = [];
|
|
@@ -156,7 +133,7 @@ export class ConnectionManager {
|
|
|
156
133
|
): Promise<void> {
|
|
157
134
|
try {
|
|
158
135
|
await this.smartSecret.setSecret(conn.id, conn.token);
|
|
159
|
-
// Save sentinel to
|
|
136
|
+
// Save sentinel to the stored record
|
|
160
137
|
const jsonConn = { ...conn, token: `${KEYCHAIN_PREFIX}${conn.id}` };
|
|
161
138
|
await this.storageManager.setJSON(`${CONNECTIONS_PREFIX}${conn.id}.json`, jsonConn);
|
|
162
139
|
logger.info(`Migrated token for connection "${conn.name}" to keychain`);
|
|
@@ -193,7 +170,7 @@ export class ConnectionManager {
|
|
|
193
170
|
private async backfillWebhookSecret(conn: interfaces.data.IProviderConnection): Promise<void> {
|
|
194
171
|
// A stored-but-unreadable secret is not a missing one. Minting here would write a new
|
|
195
172
|
// secret over the same keychain id and destroy confirmed operator material because of
|
|
196
|
-
// a transient read failure, so this connection is left exactly as it is
|
|
173
|
+
// a transient read failure, so this connection is left exactly as it is stored.
|
|
197
174
|
if (conn.webhookSecretUnreadable) return;
|
|
198
175
|
if (conn.webhookSecret && conn.webhookStatus) return;
|
|
199
176
|
if (!conn.webhookSecret) {
|
|
@@ -226,7 +203,7 @@ export class ConnectionManager {
|
|
|
226
203
|
} else {
|
|
227
204
|
await this.smartSecret.deleteSecret(getWebhookSecretId(conn.id));
|
|
228
205
|
}
|
|
229
|
-
// Save
|
|
206
|
+
// Save the record with sentinel values
|
|
230
207
|
const jsonConn = {
|
|
231
208
|
...conn,
|
|
232
209
|
token: `${KEYCHAIN_PREFIX}${conn.id}`,
|
package/ts/classes/gitopsapp.ts
CHANGED
|
@@ -7,10 +7,25 @@ import { ManagedSecretsManager } from './managedsecrets.manager.js';
|
|
|
7
7
|
import { JobManager } from './jobmanager.js';
|
|
8
8
|
import { OpsServer } from '../opsserver/index.js';
|
|
9
9
|
import { StorageManager } from '../storage/index.js';
|
|
10
|
-
import {
|
|
10
|
+
import { SecretsScanService } from './secrets.scan.service.js';
|
|
11
|
+
import {
|
|
12
|
+
CacheCleaner,
|
|
13
|
+
CachedProject,
|
|
14
|
+
CachedSecret,
|
|
15
|
+
GitopsDb,
|
|
16
|
+
readGitopsDbOptionsFromEnvironment,
|
|
17
|
+
type IGitopsDbOptions,
|
|
18
|
+
} from '../db/index.js';
|
|
11
19
|
import { resolvePaths } from '../paths.js';
|
|
20
|
+
import { runStartupMigrations } from '../../ts_migrations/index.js';
|
|
12
21
|
import { unrefTimer } from '../timers.js';
|
|
13
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The connection list a development checkout kept next to itself before gitops had a
|
|
25
|
+
* database. Resolved against the working directory, exactly as the release that wrote it did.
|
|
26
|
+
*/
|
|
27
|
+
const legacyConnectionsFile = './.nogit/connections.json';
|
|
28
|
+
|
|
14
29
|
/**
|
|
15
30
|
* Main GitOps application orchestrator
|
|
16
31
|
*/
|
|
@@ -20,7 +35,7 @@ export class GitopsApp {
|
|
|
20
35
|
public connectionManager: ConnectionManager;
|
|
21
36
|
public actionLog: ActionLog;
|
|
22
37
|
public opsServer: OpsServer;
|
|
23
|
-
public
|
|
38
|
+
public db: GitopsDb;
|
|
24
39
|
public cacheCleaner: CacheCleaner;
|
|
25
40
|
public syncManager!: SyncManager;
|
|
26
41
|
public managedSecretsManager!: ManagedSecretsManager;
|
|
@@ -29,22 +44,19 @@ export class GitopsApp {
|
|
|
29
44
|
private scanIntervalId: ReturnType<typeof setInterval> | null = null;
|
|
30
45
|
private paths: ReturnType<typeof resolvePaths>;
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
/**
|
|
48
|
+
* @param dbOptions binding of the document database; read from the environment unless a
|
|
49
|
+
* caller supplies one, which is how tests bind an isolated server.
|
|
50
|
+
*/
|
|
51
|
+
constructor(dbOptions: IGitopsDbOptions = readGitopsDbOptionsFromEnvironment()) {
|
|
52
|
+
this.paths = resolvePaths();
|
|
53
|
+
this.storageManager = new StorageManager();
|
|
39
54
|
this.smartSecret = new plugins.smartsecret.SmartSecret({ service: 'gitops' });
|
|
40
55
|
this.connectionManager = new ConnectionManager(this.storageManager, this.smartSecret);
|
|
41
56
|
this.actionLog = new ActionLog(this.storageManager);
|
|
42
57
|
|
|
43
|
-
this.
|
|
44
|
-
|
|
45
|
-
dbName: 'gitops_cache',
|
|
46
|
-
});
|
|
47
|
-
this.cacheCleaner = new CacheCleaner(this.cacheDb);
|
|
58
|
+
this.db = GitopsDb.getInstance(dbOptions);
|
|
59
|
+
this.cacheCleaner = new CacheCleaner();
|
|
48
60
|
this.cacheCleaner.registerClass(CachedProject);
|
|
49
61
|
this.cacheCleaner.registerClass(CachedSecret);
|
|
50
62
|
|
|
@@ -54,8 +66,15 @@ export class GitopsApp {
|
|
|
54
66
|
async start(port = 3000): Promise<void> {
|
|
55
67
|
logger.info('Initializing GitOps...');
|
|
56
68
|
|
|
57
|
-
//
|
|
58
|
-
|
|
69
|
+
// Every record below is read through the database, so it comes up first and the
|
|
70
|
+
// pending migrations run before any manager looks at a key.
|
|
71
|
+
await this.db.start();
|
|
72
|
+
await runStartupMigrations({
|
|
73
|
+
storage: this.storageManager,
|
|
74
|
+
legacyStoragePath: this.paths.legacyStoragePath,
|
|
75
|
+
legacyConnectionsFile: legacyConnectionsFile,
|
|
76
|
+
log: (messageArg) => logger.info(messageArg),
|
|
77
|
+
});
|
|
59
78
|
|
|
60
79
|
// Initialize connection manager (loads saved connections)
|
|
61
80
|
await this.connectionManager.init();
|
|
@@ -122,7 +141,7 @@ export class GitopsApp {
|
|
|
122
141
|
await this.syncManager.stop();
|
|
123
142
|
}
|
|
124
143
|
this.cacheCleaner.stop();
|
|
125
|
-
await this.
|
|
144
|
+
await this.db.stop();
|
|
126
145
|
logger.success('GitOps shutdown complete');
|
|
127
146
|
}
|
|
128
147
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logger } from '../logging.js';
|
|
2
|
-
import type { ConnectionManager } from '
|
|
3
|
-
import { CachedSecret } from '
|
|
4
|
-
import { TTL } from '
|
|
2
|
+
import type { ConnectionManager } from './connectionmanager.js';
|
|
3
|
+
import { CachedSecret } from '../db/documents/classes.cached.secret.js';
|
|
4
|
+
import { TTL } from '../db/classes.cached.document.js';
|
|
5
5
|
import type { ISecret } from '../../ts_interfaces/data/secret.js';
|
|
6
6
|
|
|
7
7
|
export interface IScanResult {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { logger } from '../logging.js';
|
|
2
2
|
import { unrefTimer } from '../timers.js';
|
|
3
|
-
import type { CacheDb } from './classes.cachedb.js';
|
|
4
3
|
|
|
5
4
|
// deno-lint-ignore no-explicit-any
|
|
6
5
|
type DocumentClass = { getInstances: (filter: any) => Promise<{ delete: () => Promise<void> }[]> };
|
|
@@ -14,10 +13,8 @@ export class CacheCleaner {
|
|
|
14
13
|
private intervalId: ReturnType<typeof setInterval> | null = null;
|
|
15
14
|
private intervalMs: number;
|
|
16
15
|
private documentClasses: DocumentClass[] = [];
|
|
17
|
-
private cacheDb: CacheDb;
|
|
18
16
|
|
|
19
|
-
constructor(
|
|
20
|
-
this.cacheDb = cacheDb;
|
|
17
|
+
constructor(intervalMs = DEFAULT_INTERVAL_MS) {
|
|
21
18
|
this.intervalMs = intervalMs;
|
|
22
19
|
}
|
|
23
20
|
|
|
@@ -12,7 +12,7 @@ export const TTL = {
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Abstract base class for cached documents with TTL support.
|
|
15
|
-
* Extend this class and add @Collection decorator
|
|
15
|
+
* Extend this class and add a @Collection decorator bound to GitopsDb.
|
|
16
16
|
*/
|
|
17
17
|
export abstract class CachedDocument<
|
|
18
18
|
T extends CachedDocument<T>,
|