@fabrika/provider-zerops 0.9.0-dev.3 → 0.9.0-dev.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/package.json +9 -9
- package/src/provider.ts +56 -40
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabrika/provider-zerops",
|
|
3
|
-
"version": "0.9.0-dev.
|
|
3
|
+
"version": "0.9.0-dev.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"zod": "^3.25.76",
|
|
47
|
-
"@fabrika/auth": "0.9.0-dev.
|
|
48
|
-
"@fabrika/auth-core": "0.9.0-dev.
|
|
49
|
-
"@fabrika/email": "0.9.0-dev.
|
|
50
|
-
"@fabrika/iam-contract": "0.9.0-dev.
|
|
51
|
-
"@fabrika/installation-init": "0.9.0-dev.
|
|
52
|
-
"@fabrika/operations-contract": "0.9.0-dev.
|
|
53
|
-
"@fabrika/provider-contract": "0.9.0-dev.
|
|
54
|
-
"@fabrika/proxy-core": "0.9.0-dev.
|
|
47
|
+
"@fabrika/auth": "0.9.0-dev.4",
|
|
48
|
+
"@fabrika/auth-core": "0.9.0-dev.4",
|
|
49
|
+
"@fabrika/email": "0.9.0-dev.4",
|
|
50
|
+
"@fabrika/iam-contract": "0.9.0-dev.4",
|
|
51
|
+
"@fabrika/installation-init": "0.9.0-dev.4",
|
|
52
|
+
"@fabrika/operations-contract": "0.9.0-dev.4",
|
|
53
|
+
"@fabrika/provider-contract": "0.9.0-dev.4",
|
|
54
|
+
"@fabrika/proxy-core": "0.9.0-dev.4",
|
|
55
55
|
"yaml": "^2.9.1"
|
|
56
56
|
}
|
|
57
57
|
}
|
package/src/provider.ts
CHANGED
|
@@ -620,64 +620,80 @@ const writeServiceEnv = async (env: StepEnv): Promise<void> => {
|
|
|
620
620
|
assertRunning(signal)
|
|
621
621
|
// One read: every write below waits for its own user-data sync, so the build request never meets `userDataSyncRunning`.
|
|
622
622
|
const records = await zerops.api.listServiceEnv({ serviceId, signal })
|
|
623
|
+
const changes: ServiceEnvChange[] = []
|
|
624
|
+
const put = (key: string, value: string, sensitive: boolean): void => {
|
|
625
|
+
changes.push({ line: `${key}: updated`, apply: () => zerops.api.putServiceEnv({ serviceId, key, value, sensitive, signal }) })
|
|
626
|
+
}
|
|
627
|
+
const remove = (key: string, envId: string): void => {
|
|
628
|
+
changes.push({ line: `${key}: removed`, apply: () => zerops.api.deleteServiceEnv({ envId, signal }) })
|
|
629
|
+
}
|
|
623
630
|
for (const key of SERVICE_MANAGED_KEYS) {
|
|
624
631
|
const value = managedValues[key]
|
|
625
632
|
const existing = records.find((record) => record.key === key)
|
|
626
633
|
const sensitive = SENSITIVE_MANAGED_KEYS.has(key)
|
|
627
|
-
|
|
628
|
-
if (value
|
|
629
|
-
|
|
630
|
-
log(` ${key}: updated`)
|
|
631
|
-
} else if (value === null && existing !== undefined) {
|
|
632
|
-
await zerops.api.deleteServiceEnv({ envId: existing.id, signal })
|
|
633
|
-
log(` ${key}: removed`)
|
|
634
|
-
} else {
|
|
635
|
-
log(` ${key}: unchanged`)
|
|
636
|
-
}
|
|
634
|
+
if (value !== null && (sensitive || readableContent(existing) !== value)) put(key, value, sensitive)
|
|
635
|
+
else if (value === null && existing !== undefined) remove(key, existing.id)
|
|
636
|
+
else changes.push({ line: `${key}: unchanged`, apply: null })
|
|
637
637
|
}
|
|
638
638
|
const provenance = env.attempt.storageProvenance
|
|
639
639
|
const marker = records.find((record) => record.key === STORAGE_PROVENANCE_KEY)
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
await zerops.api.putServiceEnv({ serviceId, key: STORAGE_PROVENANCE_KEY, value: provenance, sensitive: false, signal })
|
|
644
|
-
log(` ${STORAGE_PROVENANCE_KEY}: updated`)
|
|
645
|
-
} else if (provenance === null && marker !== undefined) {
|
|
646
|
-
assertRunning(signal)
|
|
647
|
-
await zerops.api.deleteServiceEnv({ envId: marker.id, signal })
|
|
648
|
-
log(` ${STORAGE_PROVENANCE_KEY}: removed`)
|
|
649
|
-
}
|
|
640
|
+
// Not a credential, and the exposure read-back above reads it: it must stay readable.
|
|
641
|
+
if (typeof provenance === 'string' && readableContent(marker) !== provenance) put(STORAGE_PROVENANCE_KEY, provenance, false)
|
|
642
|
+
else if (provenance === null && marker !== undefined) remove(STORAGE_PROVENANCE_KEY, marker.id)
|
|
650
643
|
// A workload key is written like a managed key; one whose workload is gone is removed, so no stale key stays readable.
|
|
651
644
|
for (const [key, value] of Object.entries(familyVariables)) {
|
|
652
645
|
const existing = records.find((record) => record.key === key)
|
|
653
646
|
const sensitive = isSensitiveFamilyVariable(key)
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
await zerops.api.putServiceEnv({ serviceId, key, value, sensitive, signal })
|
|
657
|
-
log(` ${key}: updated`)
|
|
658
|
-
} else {
|
|
659
|
-
log(` ${key}: unchanged`)
|
|
660
|
-
}
|
|
647
|
+
if (sensitive || readableContent(existing) !== value) put(key, value, sensitive)
|
|
648
|
+
else changes.push({ line: `${key}: unchanged`, apply: null })
|
|
661
649
|
}
|
|
662
650
|
for (const record of records) {
|
|
663
|
-
if (isFrameworkFamilyVariable(record.key) && !Object.hasOwn(familyVariables, record.key))
|
|
664
|
-
assertRunning(signal)
|
|
665
|
-
await zerops.api.deleteServiceEnv({ envId: record.id, signal })
|
|
666
|
-
log(` ${record.key}: removed`)
|
|
667
|
-
}
|
|
651
|
+
if (isFrameworkFamilyVariable(record.key) && !Object.hasOwn(familyVariables, record.key)) remove(record.key, record.id)
|
|
668
652
|
}
|
|
669
653
|
// The build lifts the upload's bearer from here (`${RUNTIME_…}`), so the descriptor never holds it.
|
|
670
654
|
const bearer = env.run.artifactUpload?.bearer ?? null
|
|
671
655
|
const uploadKey = records.find((record) => record.key === FABRIKA_SOURCE_MAP_UPLOAD_KEY)
|
|
672
|
-
if (bearer !== null)
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
656
|
+
if (bearer !== null) put(FABRIKA_SOURCE_MAP_UPLOAD_KEY, bearer, true)
|
|
657
|
+
else if (uploadKey !== undefined) remove(FABRIKA_SOURCE_MAP_UPLOAD_KEY, uploadKey.id)
|
|
658
|
+
await applyServiceEnvChanges(changes, log, signal)
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/** One service variable write or removal `write-service-env` decided on, or a key it leaves as it is (`apply: null`). */
|
|
662
|
+
interface ServiceEnvChange {
|
|
663
|
+
readonly line: string
|
|
664
|
+
readonly apply: (() => Promise<void>) | null
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/** Each write waits ~3 s for its own user-data sync; concurrent writes to one service do not conflict (zerops-platform.md). */
|
|
668
|
+
const SERVICE_ENV_WRITE_CONCURRENCY = 6
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Applies the changes `SERVICE_ENV_WRITE_CONCURRENCY` at a time and waits for every started one, so no sync still runs
|
|
672
|
+
* when the step ends. A failure or a cancel starts no further write; lines follow the decision order.
|
|
673
|
+
*/
|
|
674
|
+
const applyServiceEnvChanges = async (changes: readonly ServiceEnvChange[], log: (line: string) => void, signal: AbortSignal): Promise<void> => {
|
|
675
|
+
const applied = new Set<number>()
|
|
676
|
+
const failures: unknown[] = []
|
|
677
|
+
let next = 0
|
|
678
|
+
const worker = async (): Promise<void> => {
|
|
679
|
+
while (next < changes.length && failures.length === 0 && !signal.aborted) {
|
|
680
|
+
const index = next++
|
|
681
|
+
const apply = changes[index]?.apply ?? null
|
|
682
|
+
if (apply === null) continue
|
|
683
|
+
try {
|
|
684
|
+
await apply()
|
|
685
|
+
applied.add(index)
|
|
686
|
+
} catch (error) {
|
|
687
|
+
failures.push(error)
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
await Promise.all(Array.from({ length: SERVICE_ENV_WRITE_CONCURRENCY }, worker))
|
|
692
|
+
for (const [index, change] of changes.entries()) {
|
|
693
|
+
if (change.apply === null || applied.has(index)) log(` ${change.line}`)
|
|
680
694
|
}
|
|
695
|
+
if (failures.length > 0) throw failures[0]
|
|
696
|
+
assertRunning(signal)
|
|
681
697
|
}
|
|
682
698
|
|
|
683
699
|
/** The custom autoscaling with its minimum RAM raised to `minRam`; every other field is sent back as it was read. */
|