@checkstack/gitops-backend 0.2.2 → 0.2.4
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 +21 -0
- package/package.json +1 -1
- package/src/sync/reconciler-fast-path.test.ts +137 -0
- package/src/sync/reconciler.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @checkstack/gitops-backend
|
|
2
2
|
|
|
3
|
+
## 0.2.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [8d1ef12]
|
|
8
|
+
- Updated dependencies [8d1ef12]
|
|
9
|
+
- @checkstack/common@0.7.0
|
|
10
|
+
- @checkstack/backend-api@0.13.0
|
|
11
|
+
- @checkstack/command-backend@0.1.20
|
|
12
|
+
- @checkstack/gitops-common@0.2.1
|
|
13
|
+
- @checkstack/queue-api@0.2.14
|
|
14
|
+
|
|
15
|
+
## 0.2.3
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- adc89a8: Fix GitOps engine skipping retry of failed entities
|
|
20
|
+
|
|
21
|
+
- Updated the fast-path condition in the Reconciler engine to only skip reconciliation if the entity is in a `synced` state.
|
|
22
|
+
- Prevents entities from remaining permanently stuck in an error state without being retried if the underlying YAML file is not modified.
|
|
23
|
+
|
|
3
24
|
## 0.2.2
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, it, expect, mock } from "bun:test";
|
|
2
|
+
import { reconcileProvider } from "./reconciler";
|
|
3
|
+
import { createEntityKindRegistry } from "../kind-registry";
|
|
4
|
+
import { CHECKSTACK_API_VERSION } from "@checkstack/gitops-common";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { computeHash } from "./document-parser";
|
|
7
|
+
|
|
8
|
+
const DUMMY_YAML = `apiVersion: ${CHECKSTACK_API_VERSION}
|
|
9
|
+
kind: TestKind
|
|
10
|
+
metadata:
|
|
11
|
+
name: test-entity
|
|
12
|
+
spec: {}`;
|
|
13
|
+
|
|
14
|
+
describe("GitOps reconciler fast-path retry logic", () => {
|
|
15
|
+
it("skips reconciliation if file hash matches and status is synced", async () => {
|
|
16
|
+
const hash = computeHash({ input: DUMMY_YAML });
|
|
17
|
+
let whereCalls = 0;
|
|
18
|
+
|
|
19
|
+
const mockDb = {
|
|
20
|
+
select: () => mockDb,
|
|
21
|
+
from: () => mockDb,
|
|
22
|
+
where: async () => {
|
|
23
|
+
whereCalls++;
|
|
24
|
+
if (whereCalls === 1) { // existingProvenance lookup
|
|
25
|
+
return [{
|
|
26
|
+
id: "prov-1",
|
|
27
|
+
status: "synced",
|
|
28
|
+
lastSyncHash: hash,
|
|
29
|
+
entityId: "real-id"
|
|
30
|
+
}];
|
|
31
|
+
}
|
|
32
|
+
return [];
|
|
33
|
+
},
|
|
34
|
+
update: () => mockDb,
|
|
35
|
+
set: () => mockDb,
|
|
36
|
+
insert: () => mockDb,
|
|
37
|
+
values: async () => [],
|
|
38
|
+
delete: () => mockDb,
|
|
39
|
+
} as any;
|
|
40
|
+
|
|
41
|
+
const kindRegistry = createEntityKindRegistry();
|
|
42
|
+
const reconcileMock = mock(async () => ({ entityId: "new-id" }));
|
|
43
|
+
kindRegistry.registerKind({
|
|
44
|
+
apiVersion: CHECKSTACK_API_VERSION,
|
|
45
|
+
kind: "TestKind",
|
|
46
|
+
specSchema: z.object({}),
|
|
47
|
+
reconcile: reconcileMock,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const result = await reconcileProvider({
|
|
51
|
+
providerId: "test-provider",
|
|
52
|
+
providerType: "github",
|
|
53
|
+
target: "test-target",
|
|
54
|
+
pathPattern: "*.yaml",
|
|
55
|
+
deletionPolicy: "orphan",
|
|
56
|
+
db: mockDb,
|
|
57
|
+
logger: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} } as any,
|
|
58
|
+
kindRegistry,
|
|
59
|
+
secretStore: { resolve: async () => {} } as any,
|
|
60
|
+
scraper: {
|
|
61
|
+
discoverFiles: async () => [{
|
|
62
|
+
repository: "repo",
|
|
63
|
+
filePath: "test.yaml",
|
|
64
|
+
branch: "main",
|
|
65
|
+
content: DUMMY_YAML,
|
|
66
|
+
}],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Should skip because hash matches and status is synced
|
|
71
|
+
expect(result.unchanged).toBe(1);
|
|
72
|
+
expect(result.updated).toBe(0);
|
|
73
|
+
expect(result.created).toBe(0);
|
|
74
|
+
expect(reconcileMock).not.toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("retries reconciliation if file hash matches but status is error", async () => {
|
|
78
|
+
const hash = computeHash({ input: DUMMY_YAML });
|
|
79
|
+
let whereCalls = 0;
|
|
80
|
+
|
|
81
|
+
const mockDb = {
|
|
82
|
+
select: () => mockDb,
|
|
83
|
+
from: () => mockDb,
|
|
84
|
+
where: async () => {
|
|
85
|
+
whereCalls++;
|
|
86
|
+
if (whereCalls === 1) { // existingProvenance lookup
|
|
87
|
+
return [{
|
|
88
|
+
id: "prov-1",
|
|
89
|
+
status: "error",
|
|
90
|
+
lastSyncHash: hash,
|
|
91
|
+
entityId: "pending-123"
|
|
92
|
+
}];
|
|
93
|
+
}
|
|
94
|
+
return [];
|
|
95
|
+
},
|
|
96
|
+
update: () => mockDb,
|
|
97
|
+
set: () => mockDb,
|
|
98
|
+
insert: () => mockDb,
|
|
99
|
+
values: async () => [],
|
|
100
|
+
delete: () => mockDb,
|
|
101
|
+
} as any;
|
|
102
|
+
|
|
103
|
+
const kindRegistry = createEntityKindRegistry();
|
|
104
|
+
const reconcileMock = mock(async () => ({ entityId: "new-id" }));
|
|
105
|
+
kindRegistry.registerKind({
|
|
106
|
+
apiVersion: CHECKSTACK_API_VERSION,
|
|
107
|
+
kind: "TestKind",
|
|
108
|
+
specSchema: z.object({}),
|
|
109
|
+
reconcile: reconcileMock,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = await reconcileProvider({
|
|
113
|
+
providerId: "test-provider",
|
|
114
|
+
providerType: "github",
|
|
115
|
+
target: "test-target",
|
|
116
|
+
pathPattern: "*.yaml",
|
|
117
|
+
deletionPolicy: "orphan",
|
|
118
|
+
db: mockDb,
|
|
119
|
+
logger: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} } as any,
|
|
120
|
+
kindRegistry,
|
|
121
|
+
secretStore: { resolve: async () => {} } as any,
|
|
122
|
+
scraper: {
|
|
123
|
+
discoverFiles: async () => [{
|
|
124
|
+
repository: "repo",
|
|
125
|
+
filePath: "test.yaml",
|
|
126
|
+
branch: "main",
|
|
127
|
+
content: DUMMY_YAML,
|
|
128
|
+
}],
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Should retry because status is error, even if hash matches
|
|
133
|
+
expect(result.unchanged).toBe(0);
|
|
134
|
+
expect(result.updated).toBe(1);
|
|
135
|
+
expect(reconcileMock).toHaveBeenCalledTimes(1);
|
|
136
|
+
});
|
|
137
|
+
});
|
package/src/sync/reconciler.ts
CHANGED
|
@@ -298,8 +298,8 @@ async function reconcileEntity(params: {
|
|
|
298
298
|
|
|
299
299
|
const existing = existingProvenance[0];
|
|
300
300
|
|
|
301
|
-
if (existing && existing.lastSyncHash === contentHash) {
|
|
302
|
-
// Unchanged — skip reconciliation
|
|
301
|
+
if (existing && existing.status === "synced" && existing.lastSyncHash === contentHash) {
|
|
302
|
+
// Unchanged and synced — skip reconciliation
|
|
303
303
|
result.unchanged++;
|
|
304
304
|
return;
|
|
305
305
|
}
|