@checkstack/gitops-backend 0.1.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 +67 -0
- package/drizzle/0000_tense_stryfe.sql +46 -0
- package/drizzle/meta/0000_snapshot.json +310 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +37 -0
- package/src/index.ts +136 -0
- package/src/kind-registry.test.ts +262 -0
- package/src/kind-registry.ts +191 -0
- package/src/router.ts +355 -0
- package/src/schema.ts +77 -0
- package/src/scrapers/github-scraper.test.ts +355 -0
- package/src/scrapers/github-scraper.ts +263 -0
- package/src/scrapers/gitlab-scraper.test.ts +296 -0
- package/src/scrapers/gitlab-scraper.ts +242 -0
- package/src/scrapers/types.ts +52 -0
- package/src/secret-resolver.test.ts +86 -0
- package/src/secret-resolver.ts +54 -0
- package/src/sync/document-parser.test.ts +116 -0
- package/src/sync/document-parser.ts +124 -0
- package/src/sync/reconciler-delete.test.ts +123 -0
- package/src/sync/reconciler.ts +476 -0
- package/src/sync/sort-entities.test.ts +481 -0
- package/src/sync/sort-entities.ts +100 -0
- package/src/sync/sync-worker.ts +158 -0
- package/tsconfig.json +4 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @checkstack/gitops-backend
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6c40b5b: feat: add GitOps Entity System foundation — entity envelope schema, Entity Kind Registry extension point, secret field utility, secret resolution engine, provenance tracking, and RPC contract
|
|
8
|
+
- 6c40b5b: Generalized provenance system and GitOps frontend plugin
|
|
9
|
+
|
|
10
|
+
**Breaking**: `EntityKindDefinition.reconcile()` now returns `{ entityId: string }` instead of `void`. Plugins must return the plugin-specific entity ID (e.g., catalog system UUID) so the engine can store it in provenance.
|
|
11
|
+
|
|
12
|
+
- Added `entityId` column to the provenance table (non-nullable)
|
|
13
|
+
- Reconciler engine passes `existingEntityId` to plugins for updates
|
|
14
|
+
- `getProvenance` now supports lookup by `entityId` in addition to `entityName`
|
|
15
|
+
- Added provider CRUD endpoints: `createProvider`, `updateProvider`, `deleteProvider`
|
|
16
|
+
- Created `gitops-frontend` plugin with provider management, secret management, and sync status dashboard
|
|
17
|
+
- Removed `gitops_entity_name` metadata markers from catalog entities
|
|
18
|
+
- Removed `findSystemByGitOpsName`, `deleteSystemByGitOpsName` (and Group equivalents) from EntityService
|
|
19
|
+
- Added provenance-based UI locking in catalog-frontend: edit/delete/drag disabled for GitOps-managed systems and groups
|
|
20
|
+
|
|
21
|
+
- 6c40b5b: ### GitOps Ecosystem: Healthcheck Kind Registration (Phase 5)
|
|
22
|
+
|
|
23
|
+
**gitops-common**: Added required `resolveEntityRef` to `ReconcileContext`, enabling extension reconcilers to resolve cross-kind entity references (e.g., healthcheck refs in System extensions).
|
|
24
|
+
|
|
25
|
+
**gitops-backend**: Updated reconciler to populate `resolveEntityRef` by querying local provenance — no RPC round-trip needed.
|
|
26
|
+
|
|
27
|
+
**healthcheck-backend**: Registered `kind: Healthcheck` and `System → healthchecks` extension with the EntityKindRegistry:
|
|
28
|
+
|
|
29
|
+
- Validates strategy configs against registered strategy schemas at reconcile time
|
|
30
|
+
- Validates collector configs against registered collector schemas at reconcile time
|
|
31
|
+
- Manages system ↔ healthcheck associations with automatic stale removal
|
|
32
|
+
|
|
33
|
+
**healthcheck-frontend**: Added GitOps provenance locking to the HealthCheck IDE editor — GitOps-managed health checks show a lock banner and disable editing.
|
|
34
|
+
|
|
35
|
+
**catalog-backend**: Updated test fixtures for new required `resolveEntityRef` context field.
|
|
36
|
+
|
|
37
|
+
- 6c40b5b: Add GitOps discovery and sync engine (Phase 2)
|
|
38
|
+
|
|
39
|
+
- YAML document parser with multi-document support and SHA-256 content hashing for diff detection
|
|
40
|
+
- GitHub scraper: org/user repo enumeration, single-repo mode, default branch resolution, recursive Git Trees API, minimatch path filtering, Link header pagination
|
|
41
|
+
- GitLab scraper: group project enumeration (including subgroups), single-project mode, recursive tree walking, minimatch filtering, x-next-page pagination
|
|
42
|
+
- Configurable `baseUrl` per provider for GitHub Enterprise and self-managed GitLab instances
|
|
43
|
+
- Reconciliation orchestrator: scrape → parse → validate → resolve secrets → reconcile (base + extensions) → provenance tracking → orphan detection
|
|
44
|
+
- Sync worker: recurring queue jobs per provider, one-off manual trigger via triggerSync RPC
|
|
45
|
+
- Per-entity error isolation ensures individual failures don't halt the sync
|
|
46
|
+
|
|
47
|
+
- 6c40b5b: Add Kind Registry browser and developer documentation
|
|
48
|
+
|
|
49
|
+
- Added `gitopsAccess.kinds.read` access rule for standalone Kind Registry access
|
|
50
|
+
- Added `describeKinds()` method to the internal entity kind registry, serializing Zod schemas to JSON Schema
|
|
51
|
+
- Added `listKinds` RPC endpoint gated by the new access rule
|
|
52
|
+
- Created standalone Kind Registry page with schema visualization, extension listing, and auto-generated YAML examples
|
|
53
|
+
- Added Kind Registry link to the user menu
|
|
54
|
+
- Created developer documentation for entity kind and extension registration in `docs/backend/gitops-entity-kinds.md`
|
|
55
|
+
|
|
56
|
+
### Patch Changes
|
|
57
|
+
|
|
58
|
+
- 6c40b5b: Register catalog System and Group as GitOps entity kinds
|
|
59
|
+
|
|
60
|
+
- **catalog-backend**: Registers `kind: System` and `kind: Group` with the GitOps Entity Kind Registry. The catalog now supports declarative management via YAML descriptors in Git repositories. Systems and groups are reconciled using the `metadata.gitops_entity_name` marker for cross-sync identity lookup.
|
|
61
|
+
- **gitops-backend**: Wires up the delete reconciler for orphan cleanup — both automatic deletion (via `deletionPolicy: "auto"`) and manual orphan confirmation now invoke the owning plugin's `delete()` handler before removing provenance entries.
|
|
62
|
+
|
|
63
|
+
- Updated dependencies [6c40b5b]
|
|
64
|
+
- Updated dependencies [6c40b5b]
|
|
65
|
+
- Updated dependencies [6c40b5b]
|
|
66
|
+
- Updated dependencies [6c40b5b]
|
|
67
|
+
- @checkstack/gitops-common@0.1.0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
CREATE TYPE "deletion_policy" AS ENUM('orphan', 'auto');--> statement-breakpoint
|
|
2
|
+
CREATE TYPE "provenance_status" AS ENUM('synced', 'error', 'orphaned');--> statement-breakpoint
|
|
3
|
+
CREATE TYPE "provider_type" AS ENUM('github', 'gitlab');--> statement-breakpoint
|
|
4
|
+
CREATE TABLE "provenance" (
|
|
5
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
6
|
+
"api_version" text NOT NULL,
|
|
7
|
+
"kind" text NOT NULL,
|
|
8
|
+
"entity_name" text NOT NULL,
|
|
9
|
+
"entity_id" text NOT NULL,
|
|
10
|
+
"provider_id" text NOT NULL,
|
|
11
|
+
"repository" text NOT NULL,
|
|
12
|
+
"file_path" text NOT NULL,
|
|
13
|
+
"last_sync_hash" text NOT NULL,
|
|
14
|
+
"status" "provenance_status" DEFAULT 'synced' NOT NULL,
|
|
15
|
+
"error_message" text,
|
|
16
|
+
"last_synced_at" timestamp DEFAULT now() NOT NULL,
|
|
17
|
+
"created_at" timestamp DEFAULT now() NOT NULL
|
|
18
|
+
);
|
|
19
|
+
--> statement-breakpoint
|
|
20
|
+
CREATE TABLE "providers" (
|
|
21
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
22
|
+
"type" "provider_type" NOT NULL,
|
|
23
|
+
"target" text NOT NULL,
|
|
24
|
+
"path_pattern" text NOT NULL,
|
|
25
|
+
"auth_token" text,
|
|
26
|
+
"base_url" text,
|
|
27
|
+
"sync_interval" integer DEFAULT 300 NOT NULL,
|
|
28
|
+
"deletion_policy" "deletion_policy" DEFAULT 'orphan' NOT NULL,
|
|
29
|
+
"last_sync_at" timestamp,
|
|
30
|
+
"last_sync_error" text,
|
|
31
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
32
|
+
"updated_at" timestamp DEFAULT now() NOT NULL
|
|
33
|
+
);
|
|
34
|
+
--> statement-breakpoint
|
|
35
|
+
CREATE TABLE "secrets" (
|
|
36
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
37
|
+
"name" text NOT NULL,
|
|
38
|
+
"encrypted_value" text NOT NULL,
|
|
39
|
+
"description" text,
|
|
40
|
+
"created_by" text,
|
|
41
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
42
|
+
"updated_at" timestamp DEFAULT now() NOT NULL,
|
|
43
|
+
CONSTRAINT "secrets_name_unique" UNIQUE("name")
|
|
44
|
+
);
|
|
45
|
+
--> statement-breakpoint
|
|
46
|
+
ALTER TABLE "provenance" ADD CONSTRAINT "provenance_provider_id_providers_id_fk" FOREIGN KEY ("provider_id") REFERENCES "providers"("id") ON DELETE cascade ON UPDATE no action;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "01bbd45b-d962-4e98-b25d-53310f487822",
|
|
3
|
+
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
|
+
"version": "7",
|
|
5
|
+
"dialect": "postgresql",
|
|
6
|
+
"tables": {
|
|
7
|
+
"public.provenance": {
|
|
8
|
+
"name": "provenance",
|
|
9
|
+
"schema": "",
|
|
10
|
+
"columns": {
|
|
11
|
+
"id": {
|
|
12
|
+
"name": "id",
|
|
13
|
+
"type": "text",
|
|
14
|
+
"primaryKey": true,
|
|
15
|
+
"notNull": true
|
|
16
|
+
},
|
|
17
|
+
"api_version": {
|
|
18
|
+
"name": "api_version",
|
|
19
|
+
"type": "text",
|
|
20
|
+
"primaryKey": false,
|
|
21
|
+
"notNull": true
|
|
22
|
+
},
|
|
23
|
+
"kind": {
|
|
24
|
+
"name": "kind",
|
|
25
|
+
"type": "text",
|
|
26
|
+
"primaryKey": false,
|
|
27
|
+
"notNull": true
|
|
28
|
+
},
|
|
29
|
+
"entity_name": {
|
|
30
|
+
"name": "entity_name",
|
|
31
|
+
"type": "text",
|
|
32
|
+
"primaryKey": false,
|
|
33
|
+
"notNull": true
|
|
34
|
+
},
|
|
35
|
+
"entity_id": {
|
|
36
|
+
"name": "entity_id",
|
|
37
|
+
"type": "text",
|
|
38
|
+
"primaryKey": false,
|
|
39
|
+
"notNull": true
|
|
40
|
+
},
|
|
41
|
+
"provider_id": {
|
|
42
|
+
"name": "provider_id",
|
|
43
|
+
"type": "text",
|
|
44
|
+
"primaryKey": false,
|
|
45
|
+
"notNull": true
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"name": "repository",
|
|
49
|
+
"type": "text",
|
|
50
|
+
"primaryKey": false,
|
|
51
|
+
"notNull": true
|
|
52
|
+
},
|
|
53
|
+
"file_path": {
|
|
54
|
+
"name": "file_path",
|
|
55
|
+
"type": "text",
|
|
56
|
+
"primaryKey": false,
|
|
57
|
+
"notNull": true
|
|
58
|
+
},
|
|
59
|
+
"last_sync_hash": {
|
|
60
|
+
"name": "last_sync_hash",
|
|
61
|
+
"type": "text",
|
|
62
|
+
"primaryKey": false,
|
|
63
|
+
"notNull": true
|
|
64
|
+
},
|
|
65
|
+
"status": {
|
|
66
|
+
"name": "status",
|
|
67
|
+
"type": "provenance_status",
|
|
68
|
+
"typeSchema": "public",
|
|
69
|
+
"primaryKey": false,
|
|
70
|
+
"notNull": true,
|
|
71
|
+
"default": "'synced'"
|
|
72
|
+
},
|
|
73
|
+
"error_message": {
|
|
74
|
+
"name": "error_message",
|
|
75
|
+
"type": "text",
|
|
76
|
+
"primaryKey": false,
|
|
77
|
+
"notNull": false
|
|
78
|
+
},
|
|
79
|
+
"last_synced_at": {
|
|
80
|
+
"name": "last_synced_at",
|
|
81
|
+
"type": "timestamp",
|
|
82
|
+
"primaryKey": false,
|
|
83
|
+
"notNull": true,
|
|
84
|
+
"default": "now()"
|
|
85
|
+
},
|
|
86
|
+
"created_at": {
|
|
87
|
+
"name": "created_at",
|
|
88
|
+
"type": "timestamp",
|
|
89
|
+
"primaryKey": false,
|
|
90
|
+
"notNull": true,
|
|
91
|
+
"default": "now()"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"indexes": {},
|
|
95
|
+
"foreignKeys": {
|
|
96
|
+
"provenance_provider_id_providers_id_fk": {
|
|
97
|
+
"name": "provenance_provider_id_providers_id_fk",
|
|
98
|
+
"tableFrom": "provenance",
|
|
99
|
+
"tableTo": "providers",
|
|
100
|
+
"columnsFrom": [
|
|
101
|
+
"provider_id"
|
|
102
|
+
],
|
|
103
|
+
"columnsTo": [
|
|
104
|
+
"id"
|
|
105
|
+
],
|
|
106
|
+
"onDelete": "cascade",
|
|
107
|
+
"onUpdate": "no action"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"compositePrimaryKeys": {},
|
|
111
|
+
"uniqueConstraints": {},
|
|
112
|
+
"policies": {},
|
|
113
|
+
"checkConstraints": {},
|
|
114
|
+
"isRLSEnabled": false
|
|
115
|
+
},
|
|
116
|
+
"public.providers": {
|
|
117
|
+
"name": "providers",
|
|
118
|
+
"schema": "",
|
|
119
|
+
"columns": {
|
|
120
|
+
"id": {
|
|
121
|
+
"name": "id",
|
|
122
|
+
"type": "text",
|
|
123
|
+
"primaryKey": true,
|
|
124
|
+
"notNull": true
|
|
125
|
+
},
|
|
126
|
+
"type": {
|
|
127
|
+
"name": "type",
|
|
128
|
+
"type": "provider_type",
|
|
129
|
+
"typeSchema": "public",
|
|
130
|
+
"primaryKey": false,
|
|
131
|
+
"notNull": true
|
|
132
|
+
},
|
|
133
|
+
"target": {
|
|
134
|
+
"name": "target",
|
|
135
|
+
"type": "text",
|
|
136
|
+
"primaryKey": false,
|
|
137
|
+
"notNull": true
|
|
138
|
+
},
|
|
139
|
+
"path_pattern": {
|
|
140
|
+
"name": "path_pattern",
|
|
141
|
+
"type": "text",
|
|
142
|
+
"primaryKey": false,
|
|
143
|
+
"notNull": true
|
|
144
|
+
},
|
|
145
|
+
"auth_token": {
|
|
146
|
+
"name": "auth_token",
|
|
147
|
+
"type": "text",
|
|
148
|
+
"primaryKey": false,
|
|
149
|
+
"notNull": false
|
|
150
|
+
},
|
|
151
|
+
"base_url": {
|
|
152
|
+
"name": "base_url",
|
|
153
|
+
"type": "text",
|
|
154
|
+
"primaryKey": false,
|
|
155
|
+
"notNull": false
|
|
156
|
+
},
|
|
157
|
+
"sync_interval": {
|
|
158
|
+
"name": "sync_interval",
|
|
159
|
+
"type": "integer",
|
|
160
|
+
"primaryKey": false,
|
|
161
|
+
"notNull": true,
|
|
162
|
+
"default": 300
|
|
163
|
+
},
|
|
164
|
+
"deletion_policy": {
|
|
165
|
+
"name": "deletion_policy",
|
|
166
|
+
"type": "deletion_policy",
|
|
167
|
+
"typeSchema": "public",
|
|
168
|
+
"primaryKey": false,
|
|
169
|
+
"notNull": true,
|
|
170
|
+
"default": "'orphan'"
|
|
171
|
+
},
|
|
172
|
+
"last_sync_at": {
|
|
173
|
+
"name": "last_sync_at",
|
|
174
|
+
"type": "timestamp",
|
|
175
|
+
"primaryKey": false,
|
|
176
|
+
"notNull": false
|
|
177
|
+
},
|
|
178
|
+
"last_sync_error": {
|
|
179
|
+
"name": "last_sync_error",
|
|
180
|
+
"type": "text",
|
|
181
|
+
"primaryKey": false,
|
|
182
|
+
"notNull": false
|
|
183
|
+
},
|
|
184
|
+
"created_at": {
|
|
185
|
+
"name": "created_at",
|
|
186
|
+
"type": "timestamp",
|
|
187
|
+
"primaryKey": false,
|
|
188
|
+
"notNull": true,
|
|
189
|
+
"default": "now()"
|
|
190
|
+
},
|
|
191
|
+
"updated_at": {
|
|
192
|
+
"name": "updated_at",
|
|
193
|
+
"type": "timestamp",
|
|
194
|
+
"primaryKey": false,
|
|
195
|
+
"notNull": true,
|
|
196
|
+
"default": "now()"
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
"indexes": {},
|
|
200
|
+
"foreignKeys": {},
|
|
201
|
+
"compositePrimaryKeys": {},
|
|
202
|
+
"uniqueConstraints": {},
|
|
203
|
+
"policies": {},
|
|
204
|
+
"checkConstraints": {},
|
|
205
|
+
"isRLSEnabled": false
|
|
206
|
+
},
|
|
207
|
+
"public.secrets": {
|
|
208
|
+
"name": "secrets",
|
|
209
|
+
"schema": "",
|
|
210
|
+
"columns": {
|
|
211
|
+
"id": {
|
|
212
|
+
"name": "id",
|
|
213
|
+
"type": "text",
|
|
214
|
+
"primaryKey": true,
|
|
215
|
+
"notNull": true
|
|
216
|
+
},
|
|
217
|
+
"name": {
|
|
218
|
+
"name": "name",
|
|
219
|
+
"type": "text",
|
|
220
|
+
"primaryKey": false,
|
|
221
|
+
"notNull": true
|
|
222
|
+
},
|
|
223
|
+
"encrypted_value": {
|
|
224
|
+
"name": "encrypted_value",
|
|
225
|
+
"type": "text",
|
|
226
|
+
"primaryKey": false,
|
|
227
|
+
"notNull": true
|
|
228
|
+
},
|
|
229
|
+
"description": {
|
|
230
|
+
"name": "description",
|
|
231
|
+
"type": "text",
|
|
232
|
+
"primaryKey": false,
|
|
233
|
+
"notNull": false
|
|
234
|
+
},
|
|
235
|
+
"created_by": {
|
|
236
|
+
"name": "created_by",
|
|
237
|
+
"type": "text",
|
|
238
|
+
"primaryKey": false,
|
|
239
|
+
"notNull": false
|
|
240
|
+
},
|
|
241
|
+
"created_at": {
|
|
242
|
+
"name": "created_at",
|
|
243
|
+
"type": "timestamp",
|
|
244
|
+
"primaryKey": false,
|
|
245
|
+
"notNull": true,
|
|
246
|
+
"default": "now()"
|
|
247
|
+
},
|
|
248
|
+
"updated_at": {
|
|
249
|
+
"name": "updated_at",
|
|
250
|
+
"type": "timestamp",
|
|
251
|
+
"primaryKey": false,
|
|
252
|
+
"notNull": true,
|
|
253
|
+
"default": "now()"
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
"indexes": {},
|
|
257
|
+
"foreignKeys": {},
|
|
258
|
+
"compositePrimaryKeys": {},
|
|
259
|
+
"uniqueConstraints": {
|
|
260
|
+
"secrets_name_unique": {
|
|
261
|
+
"name": "secrets_name_unique",
|
|
262
|
+
"nullsNotDistinct": false,
|
|
263
|
+
"columns": [
|
|
264
|
+
"name"
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"policies": {},
|
|
269
|
+
"checkConstraints": {},
|
|
270
|
+
"isRLSEnabled": false
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
"enums": {
|
|
274
|
+
"public.deletion_policy": {
|
|
275
|
+
"name": "deletion_policy",
|
|
276
|
+
"schema": "public",
|
|
277
|
+
"values": [
|
|
278
|
+
"orphan",
|
|
279
|
+
"auto"
|
|
280
|
+
]
|
|
281
|
+
},
|
|
282
|
+
"public.provenance_status": {
|
|
283
|
+
"name": "provenance_status",
|
|
284
|
+
"schema": "public",
|
|
285
|
+
"values": [
|
|
286
|
+
"synced",
|
|
287
|
+
"error",
|
|
288
|
+
"orphaned"
|
|
289
|
+
]
|
|
290
|
+
},
|
|
291
|
+
"public.provider_type": {
|
|
292
|
+
"name": "provider_type",
|
|
293
|
+
"schema": "public",
|
|
294
|
+
"values": [
|
|
295
|
+
"github",
|
|
296
|
+
"gitlab"
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
"schemas": {},
|
|
301
|
+
"sequences": {},
|
|
302
|
+
"roles": {},
|
|
303
|
+
"policies": {},
|
|
304
|
+
"views": {},
|
|
305
|
+
"_meta": {
|
|
306
|
+
"columns": {},
|
|
307
|
+
"schemas": {},
|
|
308
|
+
"tables": {}
|
|
309
|
+
}
|
|
310
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@checkstack/gitops-backend",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"checkstack": {
|
|
7
|
+
"type": "backend"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"generate": "drizzle-kit generate",
|
|
12
|
+
"lint": "bun run lint:code",
|
|
13
|
+
"lint:code": "eslint . --max-warnings 0"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@checkstack/backend-api": "0.12.0",
|
|
17
|
+
"@checkstack/gitops-common": "0.0.1",
|
|
18
|
+
"@checkstack/common": "0.6.5",
|
|
19
|
+
"@checkstack/command-backend": "0.1.19",
|
|
20
|
+
"@checkstack/queue-api": "0.2.13",
|
|
21
|
+
"@orpc/server": "^1.13.2",
|
|
22
|
+
"drizzle-orm": "^0.45.0",
|
|
23
|
+
"minimatch": "^10.0.0",
|
|
24
|
+
"uuid": "^13.0.0",
|
|
25
|
+
"zod": "^4.2.1",
|
|
26
|
+
"yaml": "^2.7.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@checkstack/drizzle-helper": "0.0.4",
|
|
30
|
+
"@checkstack/scripts": "0.1.2",
|
|
31
|
+
"@checkstack/tsconfig": "0.0.5",
|
|
32
|
+
"@types/bun": "^1.3.5",
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
34
|
+
"@types/uuid": "^11.0.0",
|
|
35
|
+
"typescript": "^5.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBackendPlugin,
|
|
3
|
+
createExtensionPoint,
|
|
4
|
+
coreServices,
|
|
5
|
+
} from "@checkstack/backend-api";
|
|
6
|
+
import type { SafeDatabase } from "@checkstack/backend-api";
|
|
7
|
+
import {
|
|
8
|
+
pluginMetadata,
|
|
9
|
+
gitopsAccessRules,
|
|
10
|
+
gitopsContract,
|
|
11
|
+
} from "@checkstack/gitops-common";
|
|
12
|
+
import type {
|
|
13
|
+
EntityKindDefinition,
|
|
14
|
+
EntityKindExtensionDefinition,
|
|
15
|
+
EntityKindRegistry,
|
|
16
|
+
} from "@checkstack/gitops-common";
|
|
17
|
+
import { createEntityKindRegistry } from "./kind-registry";
|
|
18
|
+
import { createGitOpsRouter } from "./router";
|
|
19
|
+
import { setupSyncWorker } from "./sync/sync-worker";
|
|
20
|
+
import { decrypt } from "@checkstack/backend-api";
|
|
21
|
+
import * as schema from "./schema";
|
|
22
|
+
import { eq } from "drizzle-orm";
|
|
23
|
+
|
|
24
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
25
|
+
// Extension Points
|
|
26
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extension point for the Entity Kind Registry.
|
|
30
|
+
* Plugins use this during their `register()` phase to register entity kinds
|
|
31
|
+
* and spec extensions for the GitOps system.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* import { entityKindExtensionPoint } from "@checkstack/gitops-backend";
|
|
36
|
+
*
|
|
37
|
+
* // In your plugin's register() function:
|
|
38
|
+
* const registry = env.getExtensionPoint(entityKindExtensionPoint);
|
|
39
|
+
* registry.registerKind({
|
|
40
|
+
* apiVersion: "checkstack.io/v1alpha1",
|
|
41
|
+
* kind: "System",
|
|
42
|
+
* specSchema: z.object({ description: z.string().optional() }),
|
|
43
|
+
* reconcile: async ({ entity }) => { ... },
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export const entityKindExtensionPoint =
|
|
48
|
+
createExtensionPoint<EntityKindRegistry>("gitops.entity-kind-registry");
|
|
49
|
+
|
|
50
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
51
|
+
// Plugin Definition
|
|
52
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
53
|
+
|
|
54
|
+
export default createBackendPlugin({
|
|
55
|
+
metadata: pluginMetadata,
|
|
56
|
+
|
|
57
|
+
register(env) {
|
|
58
|
+
// Create the kind registry
|
|
59
|
+
const kindRegistry = createEntityKindRegistry();
|
|
60
|
+
|
|
61
|
+
// Register access rules
|
|
62
|
+
env.registerAccessRules(gitopsAccessRules);
|
|
63
|
+
|
|
64
|
+
// Register the Entity Kind Extension Point
|
|
65
|
+
// Other plugins call this to register their entity kinds and extensions
|
|
66
|
+
env.registerExtensionPoint(entityKindExtensionPoint, {
|
|
67
|
+
registerKind<TSpec>(definition: EntityKindDefinition<TSpec>) {
|
|
68
|
+
kindRegistry.registerKind(definition);
|
|
69
|
+
},
|
|
70
|
+
registerKindExtension<TExtensionSpec>(
|
|
71
|
+
definition: EntityKindExtensionDefinition<TExtensionSpec>,
|
|
72
|
+
) {
|
|
73
|
+
kindRegistry.registerKindExtension(definition);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
env.registerInit({
|
|
78
|
+
schema,
|
|
79
|
+
deps: {
|
|
80
|
+
logger: coreServices.logger,
|
|
81
|
+
rpc: coreServices.rpc,
|
|
82
|
+
queueManager: coreServices.queueManager,
|
|
83
|
+
},
|
|
84
|
+
init: async ({ logger, database, rpc, queueManager }) => {
|
|
85
|
+
logger.debug("🔄 Initializing GitOps Backend...");
|
|
86
|
+
|
|
87
|
+
const db = database as SafeDatabase<typeof schema>;
|
|
88
|
+
|
|
89
|
+
const router = createGitOpsRouter({ database: db, queueManager, kindRegistry });
|
|
90
|
+
rpc.registerRouter(router, gitopsContract);
|
|
91
|
+
|
|
92
|
+
logger.debug("✅ GitOps Backend initialized.");
|
|
93
|
+
},
|
|
94
|
+
afterPluginsReady: async ({ logger, database, queueManager }) => {
|
|
95
|
+
const registeredKinds = kindRegistry.getKinds();
|
|
96
|
+
logger.debug(
|
|
97
|
+
`🔄 GitOps: ${registeredKinds.length} entity kinds registered: ${registeredKinds.map((k) => k.kind).join(", ")}`,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const db = database as SafeDatabase<typeof schema>;
|
|
101
|
+
|
|
102
|
+
// Create a SecretStore backed by the secrets table
|
|
103
|
+
const secretStore = {
|
|
104
|
+
resolve: async (name: string): Promise<string> => {
|
|
105
|
+
const rows = await db
|
|
106
|
+
.select()
|
|
107
|
+
.from(schema.secrets)
|
|
108
|
+
.where(eq(schema.secrets.name, name));
|
|
109
|
+
const secret = rows[0];
|
|
110
|
+
if (!secret) {
|
|
111
|
+
throw new Error(`Secret not found: ${name}`);
|
|
112
|
+
}
|
|
113
|
+
return decrypt(secret.encryptedValue);
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Bootstrap sync worker
|
|
118
|
+
await setupSyncWorker({
|
|
119
|
+
db,
|
|
120
|
+
logger,
|
|
121
|
+
queueManager,
|
|
122
|
+
kindRegistry,
|
|
123
|
+
secretStore,
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Re-export types for consumer plugins
|
|
131
|
+
export type {
|
|
132
|
+
EntityKindDefinition,
|
|
133
|
+
EntityKindExtensionDefinition,
|
|
134
|
+
EntityKindRegistry,
|
|
135
|
+
ReconcileContext,
|
|
136
|
+
} from "@checkstack/gitops-common";
|