@firestartr/cli 2.5.0-snapshot-1 → 2.5.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/README.md +0 -2
- package/build/index.js +444 -30
- package/build/packages/catalog_common/index.d.ts +9 -0
- package/build/packages/catalog_common/src/codeowners/index.d.ts +26 -0
- package/build/packages/catalog_common/src/types/envvars.d.ts +0 -4
- package/build/packages/gh_provisioner/src/entities/ghrepo/helpers/branch_protections.d.ts +7 -0
- package/build/packages/gh_provisioner/src/entities/ghrepo/helpers/index.d.ts +1 -0
- package/build/packages/gh_provisioner/src/entities/ghrepo/index.d.ts +1 -0
- package/build/packages/operator/src/definitions.d.ts +15 -1
- package/build/packages/operator/src/processItem.blocks.d.ts +40 -0
- package/build/packages/operator/src/processItem.d.ts +2 -1
- package/build/packages/operator/src/processItem.slot.d.ts +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
# requirements
|
|
10
10
|
|
|
11
11
|
- terraform 1.6.2
|
|
12
|
-
- cdktf-cli@0.19.0
|
|
13
12
|
- lerna@7.4.1
|
|
14
13
|
|
|
15
14
|
To install it, run the next commands:
|
|
@@ -21,7 +20,6 @@ mv terraform /usr/local/bin/; \
|
|
|
21
20
|
chmod a+x /usr/local/bin/terraform; \
|
|
22
21
|
rm -f terraform_1.6.2_linux_amd64.zip; \
|
|
23
22
|
terraform --version; \
|
|
24
|
-
npm install -g cdktf-cli@0.19.0; \
|
|
25
23
|
npm install -g lerna@7.4.1;
|
|
26
24
|
```
|
|
27
25
|
|
package/build/index.js
CHANGED
|
@@ -268553,12 +268553,7 @@ var envVars;
|
|
|
268553
268553
|
envVars["s3Bucket"] = "S3_BUCKET";
|
|
268554
268554
|
envVars["s3Lock"] = "S3_LOCK";
|
|
268555
268555
|
envVars["s3Region"] = "S3_REGION";
|
|
268556
|
-
// ---- CDKTF/LOCAL VARIABLES -----------------------------------------------
|
|
268557
|
-
envVars["cdktfConfigFiles"] = "CDKTF_CONFIG_FILES";
|
|
268558
268556
|
envVars["exclusionsYamlPath"] = "EXCLUSIONS_PATH";
|
|
268559
|
-
envVars["cdktfEntityPath"] = "FIRESTARTR_CDKTF_ENTITY_PATH";
|
|
268560
|
-
envVars["cdktfDepsPath"] = "FIRESTARTR_CDKTF_DEPS_PATH";
|
|
268561
|
-
envVars["cdktfIsImport"] = "FIRESTARTR_CDKTF_IS_IMPORT";
|
|
268562
268557
|
// ---- GITHUB APP VARIABLES -----------------------------------------------
|
|
268563
268558
|
envVars["githubAppId"] = "GITHUB_APP_ID";
|
|
268564
268559
|
envVars["githubAppInstallationId"] = "GITHUB_APP_INSTALLATION_ID";
|
|
@@ -268980,6 +268975,128 @@ class SimpleTokenizer {
|
|
|
268980
268975
|
SimpleTokenizer,
|
|
268981
268976
|
});
|
|
268982
268977
|
|
|
268978
|
+
;// CONCATENATED MODULE: ../catalog_common/src/codeowners/index.ts
|
|
268979
|
+
// Common CODEOWNERS machinery (pure TypeScript/data; NO IO, NO classes)
|
|
268980
|
+
// Strictly follows GitHub CODEOWNERS syntax.
|
|
268981
|
+
// Spec: see specs/common/001-common-codeowners-machinery/spec.md
|
|
268982
|
+
|
|
268983
|
+
const OWNER_RE = /^@[\w.-]+(\/[\w.-]+)?$/;
|
|
268984
|
+
function codeowners_parse(raw) {
|
|
268985
|
+
if (typeof raw !== 'string' || raw === '')
|
|
268986
|
+
return [];
|
|
268987
|
+
return raw.split(/\r?\n/).map((line) => {
|
|
268988
|
+
const trimmed = line.trim();
|
|
268989
|
+
if (trimmed === '') {
|
|
268990
|
+
return { type: 'blank', raw: line };
|
|
268991
|
+
}
|
|
268992
|
+
if (/^#/.test(trimmed)) {
|
|
268993
|
+
return { type: 'comment', raw: line };
|
|
268994
|
+
}
|
|
268995
|
+
// Rule line: parse pattern & owners structurally, no ref resolution
|
|
268996
|
+
const tokens = trimmed.split(/\s+/);
|
|
268997
|
+
const pattern = tokens[0];
|
|
268998
|
+
const owners = tokens.slice(1);
|
|
268999
|
+
return { type: 'rule', pattern, owners, raw: line };
|
|
269000
|
+
});
|
|
269001
|
+
}
|
|
269002
|
+
function format(entries) {
|
|
269003
|
+
let end = entries.length;
|
|
269004
|
+
while (end > 0 && entries[end - 1].type === 'blank') {
|
|
269005
|
+
end -= 1;
|
|
269006
|
+
}
|
|
269007
|
+
const lines = entries.slice(0, end).map((entry) => {
|
|
269008
|
+
switch (entry.type) {
|
|
269009
|
+
case 'blank':
|
|
269010
|
+
return '';
|
|
269011
|
+
case 'comment':
|
|
269012
|
+
return entry.raw.trimEnd();
|
|
269013
|
+
case 'rule': {
|
|
269014
|
+
const owners = entry.owners ?? [];
|
|
269015
|
+
return owners.length > 0
|
|
269016
|
+
? `${entry.pattern} ${owners.join(' ')}`
|
|
269017
|
+
: `${entry.pattern}`;
|
|
269018
|
+
}
|
|
269019
|
+
}
|
|
269020
|
+
});
|
|
269021
|
+
return lines.join('\n') + '\n';
|
|
269022
|
+
}
|
|
269023
|
+
function codeowners_validate(raw) {
|
|
269024
|
+
const errors = [];
|
|
269025
|
+
const lines = raw.split(/\r?\n/);
|
|
269026
|
+
for (let i = 0; i < lines.length; i++) {
|
|
269027
|
+
const line = lines[i];
|
|
269028
|
+
if (/^\s*$/.test(line))
|
|
269029
|
+
continue;
|
|
269030
|
+
if (/^\s*#/.test(line))
|
|
269031
|
+
continue;
|
|
269032
|
+
const tokens = line.trim().split(/\s+/);
|
|
269033
|
+
const pattern = tokens[0];
|
|
269034
|
+
const owners = tokens.slice(1);
|
|
269035
|
+
if (!pattern) {
|
|
269036
|
+
errors.push(`Line ${i + 1}: empty pattern`);
|
|
269037
|
+
continue;
|
|
269038
|
+
}
|
|
269039
|
+
if (owners.length === 0) {
|
|
269040
|
+
errors.push(`Line ${i + 1}: rule must have at least one owner`);
|
|
269041
|
+
continue;
|
|
269042
|
+
}
|
|
269043
|
+
for (const owner of owners) {
|
|
269044
|
+
if (!OWNER_RE.test(owner)) {
|
|
269045
|
+
errors.push(`Line ${i + 1}: invalid owner '${owner}' (must be @username or @org/team-slug)`);
|
|
269046
|
+
}
|
|
269047
|
+
}
|
|
269048
|
+
if (line.includes('#')) {
|
|
269049
|
+
errors.push(`Line ${i + 1}: inline comments are not allowed`);
|
|
269050
|
+
}
|
|
269051
|
+
}
|
|
269052
|
+
return { valid: errors.length === 0, errors };
|
|
269053
|
+
}
|
|
269054
|
+
function updateRule(entries, pattern, owners) {
|
|
269055
|
+
const idx = entries.findIndex((e) => e.type === 'rule' && e.pattern === pattern);
|
|
269056
|
+
const newEntry = {
|
|
269057
|
+
type: 'rule',
|
|
269058
|
+
pattern,
|
|
269059
|
+
owners,
|
|
269060
|
+
raw: `${pattern} ${owners.join(' ')}`,
|
|
269061
|
+
};
|
|
269062
|
+
if (idx >= 0) {
|
|
269063
|
+
const result = [...entries];
|
|
269064
|
+
result[idx] = newEntry;
|
|
269065
|
+
return result;
|
|
269066
|
+
}
|
|
269067
|
+
return [...entries, newEntry];
|
|
269068
|
+
}
|
|
269069
|
+
function remove(entries, pattern) {
|
|
269070
|
+
return entries.filter((e) => !(e.type === 'rule' && e.pattern === pattern));
|
|
269071
|
+
}
|
|
269072
|
+
function getOwners(entries) {
|
|
269073
|
+
const owners = new Set();
|
|
269074
|
+
for (const entry of entries) {
|
|
269075
|
+
if (entry.type === 'rule' && entry.owners) {
|
|
269076
|
+
for (const owner of entry.owners) {
|
|
269077
|
+
owners.add(owner);
|
|
269078
|
+
}
|
|
269079
|
+
}
|
|
269080
|
+
}
|
|
269081
|
+
return [...owners];
|
|
269082
|
+
}
|
|
269083
|
+
function resolveRefs(raw, replacements) {
|
|
269084
|
+
const tokenizer = new SimpleTokenizer(/\{\{\s*([\w./-]+)\s*\}\}/);
|
|
269085
|
+
return tokenizer.replace(raw, (token) => {
|
|
269086
|
+
const key = token.groups?.[0] ?? '';
|
|
269087
|
+
return key in replacements ? replacements[key] : token.value;
|
|
269088
|
+
});
|
|
269089
|
+
}
|
|
269090
|
+
/* harmony default export */ const codeowners = ({
|
|
269091
|
+
parse: codeowners_parse,
|
|
269092
|
+
format,
|
|
269093
|
+
validate: codeowners_validate,
|
|
269094
|
+
updateRule,
|
|
269095
|
+
remove,
|
|
269096
|
+
getOwners,
|
|
269097
|
+
resolveRefs,
|
|
269098
|
+
});
|
|
269099
|
+
|
|
268983
269100
|
// EXTERNAL MODULE: ../../node_modules/cron-parser/dist/index.js
|
|
268984
269101
|
var cron_parser_dist = __nccwpck_require__(98370);
|
|
268985
269102
|
;// CONCATENATED MODULE: ../catalog_common/src/cron.ts
|
|
@@ -269023,6 +269140,7 @@ function getCronNextInterval(cronLine, tz) {
|
|
|
269023
269140
|
|
|
269024
269141
|
|
|
269025
269142
|
|
|
269143
|
+
|
|
269026
269144
|
/* harmony default export */ const catalog_common = ({
|
|
269027
269145
|
io: io,
|
|
269028
269146
|
generic: generic,
|
|
@@ -269033,6 +269151,7 @@ function getCronNextInterval(cronLine, tz) {
|
|
|
269033
269151
|
policies: policies,
|
|
269034
269152
|
logger: logger_logger,
|
|
269035
269153
|
tokenizer: tokenizer,
|
|
269154
|
+
codeowners: codeowners,
|
|
269036
269155
|
cron: {
|
|
269037
269156
|
validateCron: validateCron,
|
|
269038
269157
|
isValidCron: isValidCron,
|
|
@@ -290043,7 +290162,7 @@ class RepoGithubDecanter extends GithubDecanter {
|
|
|
290043
290162
|
if (this.data.isEmpty) {
|
|
290044
290163
|
const repoFullName = `${this.org}/${this.data.repoDetails.name}`;
|
|
290045
290164
|
throw new Error(`The repo ${repoFullName} is empty - no commits, therefore CAN NOT be imported. ` +
|
|
290046
|
-
'Create it with
|
|
290165
|
+
'Create it with Firestartr or fill it with some README.md or another initial file or code');
|
|
290047
290166
|
}
|
|
290048
290167
|
}
|
|
290049
290168
|
constructor(data, org, githubTeams = {}) {
|
|
@@ -291151,6 +291270,27 @@ function definitions_getPluralFromKind(kind) {
|
|
|
291151
291270
|
const plural = Object.keys(kindPluralMap).find((key) => kindPluralMap[key] === kind);
|
|
291152
291271
|
return plural;
|
|
291153
291272
|
}
|
|
291273
|
+
// ----------------------------------------------------
|
|
291274
|
+
// Operations, workitems and handlers definitions
|
|
291275
|
+
// ----------------------------------------------------
|
|
291276
|
+
/**
|
|
291277
|
+
* Helper to extract the canonical identity (metadata.uid) from a WorkItem or its item.
|
|
291278
|
+
*
|
|
291279
|
+
* Use this helper when you need a stable identity key for WorkItems, e.g. for
|
|
291280
|
+
* in-memory deferred-scheduling bookkeeping.
|
|
291281
|
+
*
|
|
291282
|
+
* Note: other subsystems may still use kind/namespace/name keys; this helper is
|
|
291283
|
+
* specifically intended for UID-based WorkItem identity.
|
|
291284
|
+
* Any future change to identity logic MUST be made here and nowhere else.
|
|
291285
|
+
*
|
|
291286
|
+
* Returns undefined if .metadata.uid is missing so scheduler/bookkeeping paths
|
|
291287
|
+
* can safely short-circuit instead of throwing and aborting an entire pass.
|
|
291288
|
+
*/
|
|
291289
|
+
function getWorkItemIdentity(wi) {
|
|
291290
|
+
// Support either .item.metadata.uid or direct .metadata.uid for future extensibility
|
|
291291
|
+
const meta = wi.item && wi.item.metadata ? wi.item.metadata : wi.metadata;
|
|
291292
|
+
return meta && meta.uid ? meta.uid : undefined;
|
|
291293
|
+
}
|
|
291154
291294
|
var OperationType;
|
|
291155
291295
|
(function (OperationType) {
|
|
291156
291296
|
OperationType["RENAMED"] = "RENAMED";
|
|
@@ -293242,7 +293382,7 @@ async function* markedToDeletion(item, op, handler) {
|
|
|
293242
293382
|
reason: op,
|
|
293243
293383
|
type: 'PROVISIONED',
|
|
293244
293384
|
status: 'False',
|
|
293245
|
-
message: 'Synth
|
|
293385
|
+
message: 'Synth',
|
|
293246
293386
|
};
|
|
293247
293387
|
yield {
|
|
293248
293388
|
item,
|
|
@@ -293394,6 +293534,134 @@ function fdummies_fWait(ms) {
|
|
|
293394
293534
|
});
|
|
293395
293535
|
}
|
|
293396
293536
|
|
|
293537
|
+
;// CONCATENATED MODULE: ../operator/src/processItem.blocks.ts
|
|
293538
|
+
// processItem.blocks.ts
|
|
293539
|
+
// Responsible for all in-memory deferred-scheduling bookkeeping for blocked parent deletions.
|
|
293540
|
+
//
|
|
293541
|
+
// Always extract WorkItem identity for all in-memory maps and keys by calling
|
|
293542
|
+
// getWorkItemIdentity() from definitions.ts, and NOT by constructing your own key.
|
|
293543
|
+
// This ensures spec compliance and future maintainability.
|
|
293544
|
+
//
|
|
293545
|
+
// Does NOT detect "blocked" state (caller must supply outcome). Purely key/value scheduler-local state logic per spec.
|
|
293546
|
+
|
|
293547
|
+
// Only use getWorkItemIdentity(item) for identity bookkeeping and maps.
|
|
293548
|
+
// --- Config: Deferral Policy ---
|
|
293549
|
+
/**
|
|
293550
|
+
* Maximum number of blocked attempts allowed before an item is moved to the deferred segment.
|
|
293551
|
+
* An item becomes deferred once its blocked-attempt count exceeds this value.
|
|
293552
|
+
*/
|
|
293553
|
+
const MAX_BLOCKED_ATTEMPTS_BEFORE_DEFER = 5;
|
|
293554
|
+
// Initial supported parent kind(s): extensible for future cases
|
|
293555
|
+
const SUPPORTED_PARENT_KINDS = new Set(['FirestartrGithubRepository']);
|
|
293556
|
+
// --- Bookkeeper Class ---
|
|
293557
|
+
/**
|
|
293558
|
+
* Encapsulates all deferred-scheduling bookkeeping state for one scheduler queue.
|
|
293559
|
+
* Instantiate once per queue (alongside processItemSlotsManager) inside loop().
|
|
293560
|
+
*/
|
|
293561
|
+
class DeferredSchedulingBookkeeper {
|
|
293562
|
+
constructor() {
|
|
293563
|
+
this._blockedAttempts = new Map();
|
|
293564
|
+
this._deferredEntryOrder = new Map();
|
|
293565
|
+
this._deferredOrderCounter = 0;
|
|
293566
|
+
}
|
|
293567
|
+
// --- Applicability Logic ---
|
|
293568
|
+
/**
|
|
293569
|
+
* Returns true only for supported parent kinds AND only for deletion operations.
|
|
293570
|
+
* Per spec Definition 2: applicable when operation is MARKED_TO_DELETION,
|
|
293571
|
+
* or RETRY with metadata.deletionTimestamp set.
|
|
293572
|
+
*/
|
|
293573
|
+
isDeferredSchedulingApplicable(item) {
|
|
293574
|
+
const kind = item.item && item.item.kind ? item.item.kind : item.kind;
|
|
293575
|
+
if (!SUPPORTED_PARENT_KINDS.has(kind))
|
|
293576
|
+
return false;
|
|
293577
|
+
const op = item.operation;
|
|
293578
|
+
if (op === OperationType.MARKED_TO_DELETION)
|
|
293579
|
+
return true;
|
|
293580
|
+
if (op === OperationType.RETRY && item.item?.metadata?.deletionTimestamp)
|
|
293581
|
+
return true;
|
|
293582
|
+
return false;
|
|
293583
|
+
}
|
|
293584
|
+
// --- Bookkeeping API ---
|
|
293585
|
+
/**
|
|
293586
|
+
* Increments block counter for item (only call when item has been detected as blocked).
|
|
293587
|
+
* Returns new value. Promotes item to deferred segment if threshold crossed.
|
|
293588
|
+
*/
|
|
293589
|
+
incrementBlockedAttempt(item) {
|
|
293590
|
+
if (!this.isDeferredSchedulingApplicable(item))
|
|
293591
|
+
return 0;
|
|
293592
|
+
const id = getWorkItemIdentity(item);
|
|
293593
|
+
if (id === undefined)
|
|
293594
|
+
return 0;
|
|
293595
|
+
const prev = this._blockedAttempts.get(id) || 0;
|
|
293596
|
+
const next = prev + 1;
|
|
293597
|
+
this._blockedAttempts.set(id, next);
|
|
293598
|
+
if (next > MAX_BLOCKED_ATTEMPTS_BEFORE_DEFER &&
|
|
293599
|
+
!this._deferredEntryOrder.has(id)) {
|
|
293600
|
+
this._deferredEntryOrder.set(id, ++this._deferredOrderCounter);
|
|
293601
|
+
}
|
|
293602
|
+
return next;
|
|
293603
|
+
}
|
|
293604
|
+
getBlockedAttempts(item) {
|
|
293605
|
+
if (!this.isDeferredSchedulingApplicable(item))
|
|
293606
|
+
return 0;
|
|
293607
|
+
const id = getWorkItemIdentity(item);
|
|
293608
|
+
if (id === undefined)
|
|
293609
|
+
return 0;
|
|
293610
|
+
return this._blockedAttempts.get(id) || 0;
|
|
293611
|
+
}
|
|
293612
|
+
isDeferred(item) {
|
|
293613
|
+
if (!this.isDeferredSchedulingApplicable(item))
|
|
293614
|
+
return false;
|
|
293615
|
+
return this.getBlockedAttempts(item) > MAX_BLOCKED_ATTEMPTS_BEFORE_DEFER;
|
|
293616
|
+
}
|
|
293617
|
+
getDeferredOrder(item) {
|
|
293618
|
+
if (!this.isDeferredSchedulingApplicable(item))
|
|
293619
|
+
return undefined;
|
|
293620
|
+
const id = getWorkItemIdentity(item);
|
|
293621
|
+
if (id === undefined)
|
|
293622
|
+
return undefined;
|
|
293623
|
+
return this._deferredEntryOrder.get(id);
|
|
293624
|
+
}
|
|
293625
|
+
removeBookkeeping(item) {
|
|
293626
|
+
if (!this.isDeferredSchedulingApplicable(item))
|
|
293627
|
+
return;
|
|
293628
|
+
const id = getWorkItemIdentity(item);
|
|
293629
|
+
if (id === undefined)
|
|
293630
|
+
return;
|
|
293631
|
+
this._blockedAttempts.delete(id);
|
|
293632
|
+
this._deferredEntryOrder.delete(id);
|
|
293633
|
+
}
|
|
293634
|
+
// --- Queue Partitioning ---
|
|
293635
|
+
/**
|
|
293636
|
+
* Splits queue into non-deferred and deferred, preserving original order,
|
|
293637
|
+
* but sorts deferred by stable insertion order.
|
|
293638
|
+
*/
|
|
293639
|
+
partitionQueueByDeferred(queue) {
|
|
293640
|
+
const nonDeferred = [];
|
|
293641
|
+
const deferred = [];
|
|
293642
|
+
for (const item of queue) {
|
|
293643
|
+
if (this.isDeferredSchedulingApplicable(item) && this.isDeferred(item)) {
|
|
293644
|
+
deferred.push(item);
|
|
293645
|
+
}
|
|
293646
|
+
else {
|
|
293647
|
+
nonDeferred.push(item);
|
|
293648
|
+
}
|
|
293649
|
+
}
|
|
293650
|
+
deferred.sort((a, b) => {
|
|
293651
|
+
const orderA = this.getDeferredOrder(a) || 0;
|
|
293652
|
+
const orderB = this.getDeferredOrder(b) || 0;
|
|
293653
|
+
return orderA - orderB;
|
|
293654
|
+
});
|
|
293655
|
+
return { nonDeferred, deferred };
|
|
293656
|
+
}
|
|
293657
|
+
// --- TESTING / DEBUG ---
|
|
293658
|
+
clearAllBookkeeping() {
|
|
293659
|
+
this._blockedAttempts.clear();
|
|
293660
|
+
this._deferredEntryOrder.clear();
|
|
293661
|
+
this._deferredOrderCounter = 0;
|
|
293662
|
+
}
|
|
293663
|
+
}
|
|
293664
|
+
|
|
293397
293665
|
;// CONCATENATED MODULE: ../operator/src/processItem.debug.ts
|
|
293398
293666
|
|
|
293399
293667
|
|
|
@@ -293884,12 +294152,12 @@ async function startCRStates(meter, kindList, namespace) {
|
|
|
293884
294152
|
|
|
293885
294153
|
|
|
293886
294154
|
|
|
293887
|
-
function initProcessItemsSlots(nSlots, initMetrics = true) {
|
|
294155
|
+
function initProcessItemsSlots(nSlots, initMetrics = true, onWorkItemBlockedByHandler) {
|
|
293888
294156
|
operator_src_logger.info(`Initializing ${nSlots} processing slots...`);
|
|
293889
|
-
return new ProcessItemSlots(nSlots, initMetrics);
|
|
294157
|
+
return new ProcessItemSlots(nSlots, initMetrics, onWorkItemBlockedByHandler);
|
|
293890
294158
|
}
|
|
293891
294159
|
class ProcessItemSlots {
|
|
293892
|
-
constructor(nSlots, initMetrics) {
|
|
294160
|
+
constructor(nSlots, initMetrics, onWorkItemBlockedByHandler) {
|
|
293893
294161
|
this._guardRails = new GuardRails();
|
|
293894
294162
|
// let's init the slots
|
|
293895
294163
|
this._nSlots = nSlots;
|
|
@@ -293906,6 +294174,7 @@ class ProcessItemSlots {
|
|
|
293906
294174
|
slot.actions = {
|
|
293907
294175
|
blockItem: (item) => this._guardRails.block(item),
|
|
293908
294176
|
unblockItem: (item) => this._guardRails.unblock(item),
|
|
294177
|
+
onWorkItemBlockedByHandler,
|
|
293909
294178
|
};
|
|
293910
294179
|
return slot;
|
|
293911
294180
|
});
|
|
@@ -294016,6 +294285,9 @@ class ProcessItemSlot {
|
|
|
294016
294285
|
if ('needsBlocking' in workItem.handler &&
|
|
294017
294286
|
workItem.handler.needsBlocking(item, workItem.operation)) {
|
|
294018
294287
|
operator_src_logger.debug(`Item ${item.kind}/${item.metadata.namespace} needs blocking`);
|
|
294288
|
+
// Fire-and-forget: the callback is synchronous bookkeeping only;
|
|
294289
|
+
// no async side effects are expected from this callback.
|
|
294290
|
+
this._actions?.onWorkItemBlockedByHandler?.(workItem);
|
|
294019
294291
|
return;
|
|
294020
294292
|
}
|
|
294021
294293
|
workItem.workStatus = WorkStatus.PROCESSING;
|
|
@@ -294037,6 +294309,7 @@ class ProcessItemSlot {
|
|
|
294037
294309
|
|
|
294038
294310
|
|
|
294039
294311
|
|
|
294312
|
+
|
|
294040
294313
|
const queue = [];
|
|
294041
294314
|
const WEIGHTS = {
|
|
294042
294315
|
RENAMED: 15,
|
|
@@ -294157,12 +294430,26 @@ async function processItem(workItem) {
|
|
|
294157
294430
|
async function processItem_loop() {
|
|
294158
294431
|
loopWorkItemDebug(queue);
|
|
294159
294432
|
let podIsTerminating = false;
|
|
294160
|
-
const
|
|
294161
|
-
const
|
|
294162
|
-
|
|
294163
|
-
|
|
294164
|
-
|
|
294165
|
-
|
|
294433
|
+
const deferredBookkeeper = new DeferredSchedulingBookkeeper();
|
|
294434
|
+
const processItemSlotsManager = initProcessItemsSlots(MAX_SLOTS, true, (w) => deferredBookkeeper.incrementBlockedAttempt(w));
|
|
294435
|
+
if (process.env.GARBAGE_QUEUE_COLLECTOR) {
|
|
294436
|
+
void workItemGarbageCollector(queue, deferredBookkeeper);
|
|
294437
|
+
}
|
|
294438
|
+
const nextWorkItem = () => {
|
|
294439
|
+
// First, sort the queue and partition it for deferred scheduling.
|
|
294440
|
+
const sorted = sortQueue(queue);
|
|
294441
|
+
const { nonDeferred, deferred } = deferredBookkeeper.partitionQueueByDeferred(sorted);
|
|
294442
|
+
// Helper for selection logic
|
|
294443
|
+
function pickEligible(arr) {
|
|
294444
|
+
return arr
|
|
294445
|
+
.filter((w) => !w.isBlocked &&
|
|
294446
|
+
!w.isPicked &&
|
|
294447
|
+
!processItemSlotsManager.isWorkItemBlocked(w))
|
|
294448
|
+
.find((w) => w.workStatus === WorkStatus.PENDING);
|
|
294449
|
+
}
|
|
294450
|
+
// Always prefer non-deferred, only defer if absolutely nothing eligible
|
|
294451
|
+
return pickEligible(nonDeferred) || pickEligible(deferred);
|
|
294452
|
+
};
|
|
294166
294453
|
initSignalsHandler(new Map([['SIGTERM', () => (podIsTerminating = true)]]));
|
|
294167
294454
|
operator_src_logger.info(`
|
|
294168
294455
|
------- Processor main loop started with a maximum of '${MAX_SLOTS}' concurrent slots to process items. -------
|
|
@@ -294239,24 +294526,23 @@ function processItem_wait(t = 2000) {
|
|
|
294239
294526
|
* Periodically checks the queue for finished WorkItems and removes them
|
|
294240
294527
|
* @param {WorkItem[]} queue - Queue of WorkItems
|
|
294241
294528
|
*/
|
|
294242
|
-
async function workItemGarbageCollector(queue) {
|
|
294529
|
+
async function workItemGarbageCollector(queue, bookkeeper) {
|
|
294243
294530
|
while (1) {
|
|
294244
294531
|
operator_src_logger.debug(`The garbage collector processed '${queue.length}' work items.`);
|
|
294245
|
-
for (
|
|
294532
|
+
for (let i = queue.length - 1; i >= 0; i--) {
|
|
294533
|
+
const wi = queue[i];
|
|
294246
294534
|
if (wi.workStatus === WorkStatus.FINISHED) {
|
|
294535
|
+
bookkeeper?.removeBookkeeping(wi);
|
|
294247
294536
|
// Because the queue is a constant, we cannot reassign it, instead we
|
|
294248
294537
|
// use splice which modifies the array in place
|
|
294249
294538
|
//wi.onDelete()
|
|
294250
|
-
queue.splice(
|
|
294539
|
+
queue.splice(i, 1);
|
|
294251
294540
|
}
|
|
294252
294541
|
}
|
|
294253
294542
|
operator_src_logger.debug(`The garbage collector finished its run, leaving '${queue.length}' work items in the queue.`);
|
|
294254
294543
|
await processItem_wait(10 * 1000);
|
|
294255
294544
|
}
|
|
294256
294545
|
}
|
|
294257
|
-
if (process.env.GARBAGE_QUEUE_COLLECTOR) {
|
|
294258
|
-
void workItemGarbageCollector(queue);
|
|
294259
|
-
}
|
|
294260
294546
|
|
|
294261
294547
|
;// CONCATENATED MODULE: ../terraform_provisioner/src/tf/analyzer.ts
|
|
294262
294548
|
|
|
@@ -296792,7 +297078,7 @@ async function* process_operation_markedToDeletion(item, op, handler) {
|
|
|
296792
297078
|
reason: op,
|
|
296793
297079
|
type: 'PROVISIONED',
|
|
296794
297080
|
status: 'False',
|
|
296795
|
-
message: 'Synth
|
|
297081
|
+
message: 'Synth',
|
|
296796
297082
|
};
|
|
296797
297083
|
yield {
|
|
296798
297084
|
item,
|
|
@@ -296914,7 +297200,7 @@ async function* doApply(item, op, handler) {
|
|
|
296914
297200
|
reason: op,
|
|
296915
297201
|
type: 'PROVISIONED',
|
|
296916
297202
|
status: 'False',
|
|
296917
|
-
message: 'Synth
|
|
297203
|
+
message: 'Synth',
|
|
296918
297204
|
};
|
|
296919
297205
|
let output = '';
|
|
296920
297206
|
const type = 'PROVISIONING';
|
|
@@ -298218,8 +298504,8 @@ const MODULES = {
|
|
|
298218
298504
|
},
|
|
298219
298505
|
FirestartrGithubRepository: {
|
|
298220
298506
|
module: 'git::https://github.com/prefapp/tfm.git//modules/github-repo',
|
|
298221
|
-
// github-repo-v0.
|
|
298222
|
-
ref: '
|
|
298507
|
+
// github-repo-v0.5.1
|
|
298508
|
+
ref: '67c7afaec38c1fdc9f1928090c181ef310fb81ec',
|
|
298223
298509
|
},
|
|
298224
298510
|
FirestartrGithubRepositoryFeature: {
|
|
298225
298511
|
module: 'git::https://github.com/prefapp/tfm.git//modules/github-files-set',
|
|
@@ -298683,16 +298969,67 @@ function provisionDefaultBranch(fsGithubRepository) {
|
|
|
298683
298969
|
;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/helpers/codeowners.ts
|
|
298684
298970
|
|
|
298685
298971
|
|
|
298972
|
+
// Helper: Escape RegExp special characters in owner strings
|
|
298973
|
+
function codeowners_escapeRegExp(string) {
|
|
298974
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
298975
|
+
}
|
|
298686
298976
|
async function provisionCodeowners(fsGithubRepository) {
|
|
298687
298977
|
const cr = fsGithubRepository.cr;
|
|
298978
|
+
let codeownersText = cr.spec.repo.codeowners;
|
|
298979
|
+
const org = cr.spec.org;
|
|
298980
|
+
if (typeof codeownersText !== 'string') {
|
|
298981
|
+
gh_provisioner_src_logger.debug('[provisionCodeowners] No CODEOWNERS content configured; skipping provisioning.');
|
|
298982
|
+
return;
|
|
298983
|
+
}
|
|
298984
|
+
// 1. Gather all group refs present in cr.spec.permissions (if any)
|
|
298985
|
+
const allRefs = [];
|
|
298986
|
+
if (cr.spec.permissions && Array.isArray(cr.spec.permissions)) {
|
|
298987
|
+
for (const perm of cr.spec.permissions) {
|
|
298988
|
+
if (perm.ref && perm.ref.kind === 'FirestartrGithubGroup') {
|
|
298989
|
+
allRefs.push(perm.ref);
|
|
298990
|
+
}
|
|
298991
|
+
}
|
|
298992
|
+
}
|
|
298993
|
+
// (Extend here if group refs appear in other fields in future specs)
|
|
298994
|
+
// 2. For each group dependency: get external name & slug
|
|
298995
|
+
const replacements = [];
|
|
298996
|
+
for (const ref of allRefs) {
|
|
298997
|
+
let dep;
|
|
298998
|
+
try {
|
|
298999
|
+
dep = base_Entity.refResolver(ref);
|
|
299000
|
+
}
|
|
299001
|
+
catch (e) {
|
|
299002
|
+
gh_provisioner_src_logger.error(`[provisionCodeowners] Failed to resolve group ref ${ref.name}: ${String(e)}`);
|
|
299003
|
+
continue;
|
|
299004
|
+
}
|
|
299005
|
+
const extName = dep?.cr?.metadata?.annotations?.['firestartr.dev/external-name'];
|
|
299006
|
+
const slug = dep?.getOutput && dep.getOutput('slug');
|
|
299007
|
+
if (!extName) {
|
|
299008
|
+
gh_provisioner_src_logger.error(`[provisionCodeowners] Missing external-name annotation for group ref ${ref.name}`);
|
|
299009
|
+
continue;
|
|
299010
|
+
}
|
|
299011
|
+
if (!slug) {
|
|
299012
|
+
gh_provisioner_src_logger.error(`[provisionCodeowners] Missing slug output for group '${extName}' (${ref.name}) - will preserve as-is.`);
|
|
299013
|
+
continue;
|
|
299014
|
+
}
|
|
299015
|
+
const from = `@${org}/${extName}`;
|
|
299016
|
+
const to = `@${org}/${slug}`;
|
|
299017
|
+
replacements.push({ from, to });
|
|
299018
|
+
}
|
|
299019
|
+
// 3. Replace all exact occurrences of each external name owner with slug owner
|
|
299020
|
+
for (const { from, to } of replacements) {
|
|
299021
|
+
// Needs literal match, even if source owner has spaces/punctuation
|
|
299022
|
+
codeownersText = codeownersText.replace(new RegExp(codeowners_escapeRegExp(from), 'g'), to);
|
|
299023
|
+
}
|
|
299024
|
+
// Do not reformat, parse, or canonicalize further (per spec: preserve layout except for replacements)
|
|
298688
299025
|
const config = {
|
|
298689
299026
|
branch: cr.spec.repo.defaultBranch,
|
|
298690
299027
|
commitMessage: 'ci: provision CODEOWNERS file',
|
|
298691
|
-
content:
|
|
299028
|
+
content: codeownersText,
|
|
298692
299029
|
file: '.github/CODEOWNERS',
|
|
298693
299030
|
overwriteOnCreate: true,
|
|
298694
299031
|
};
|
|
298695
|
-
gh_provisioner_src_logger.debug(`
|
|
299032
|
+
gh_provisioner_src_logger.debug(`[provisionCodeowners] Final CODEOWNERS content: ${codeownersText}`);
|
|
298696
299033
|
fsGithubRepository.patchData({
|
|
298697
299034
|
path: '/config/files/-',
|
|
298698
299035
|
op: PatchOperations.add,
|
|
@@ -298886,6 +299223,48 @@ function checkIfLabelExists(labelName, repoIssuesList) {
|
|
|
298886
299223
|
return repoIssuesList.some((label) => label.name === labelName);
|
|
298887
299224
|
}
|
|
298888
299225
|
|
|
299226
|
+
;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/helpers/branch_protections.ts
|
|
299227
|
+
|
|
299228
|
+
|
|
299229
|
+
/**
|
|
299230
|
+
* Adds branch protection config to the synthesized Terraform config,
|
|
299231
|
+
* preserving legacy field semantics exactly (empty lists, explicit booleans, names).
|
|
299232
|
+
* @param entity The EntityGHRepo instance
|
|
299233
|
+
*/
|
|
299234
|
+
function provisionBranchProtections(entity) {
|
|
299235
|
+
const protections = entity.cr.spec.branchProtections;
|
|
299236
|
+
if (!protections || !Array.isArray(protections) || protections.length === 0) {
|
|
299237
|
+
return;
|
|
299238
|
+
}
|
|
299239
|
+
for (const protection of protections) {
|
|
299240
|
+
// This shape should match the compatible Terraform contract
|
|
299241
|
+
const output = {};
|
|
299242
|
+
// Only emit legacy fields in allowed order
|
|
299243
|
+
output.branch = protection.branch;
|
|
299244
|
+
output.statusChecks =
|
|
299245
|
+
'statusChecks' in protection ? (protection.statusChecks ?? []) : [];
|
|
299246
|
+
if ('requiredReviewersCount' in protection)
|
|
299247
|
+
output.requiredReviewersCount = protection.requiredReviewersCount;
|
|
299248
|
+
if ('requiredCodeownersReviewers' in protection)
|
|
299249
|
+
output.requiredCodeownersReviewers =
|
|
299250
|
+
protection.requiredCodeownersReviewers;
|
|
299251
|
+
if ('enforceAdmins' in protection)
|
|
299252
|
+
output.enforceAdmins = protection.enforceAdmins;
|
|
299253
|
+
if ('requireSignedCommits' in protection)
|
|
299254
|
+
output.requireSignedCommits = protection.requireSignedCommits;
|
|
299255
|
+
if ('requireConversationResolution' in protection)
|
|
299256
|
+
output.requireConversationResolution =
|
|
299257
|
+
protection.requireConversationResolution;
|
|
299258
|
+
// Only keep fields that are present, including explicit false/empty
|
|
299259
|
+
gh_provisioner_src_logger.debug(`[gh-provisioner] Adding branch protection config: ${JSON.stringify(output)}`);
|
|
299260
|
+
entity.patchData({
|
|
299261
|
+
op: PatchOperations.add,
|
|
299262
|
+
path: '/config/branch_protections/-',
|
|
299263
|
+
value: output,
|
|
299264
|
+
});
|
|
299265
|
+
}
|
|
299266
|
+
}
|
|
299267
|
+
|
|
298889
299268
|
;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/helpers/index.ts
|
|
298890
299269
|
|
|
298891
299270
|
|
|
@@ -298894,6 +299273,7 @@ function checkIfLabelExists(labelName, repoIssuesList) {
|
|
|
298894
299273
|
|
|
298895
299274
|
|
|
298896
299275
|
|
|
299276
|
+
|
|
298897
299277
|
;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/post/additional_branches.ts
|
|
298898
299278
|
|
|
298899
299279
|
|
|
@@ -298960,6 +299340,9 @@ async function provisionRegularBranch(repo, branchName, sourceBranch, org) {
|
|
|
298960
299340
|
|
|
298961
299341
|
|
|
298962
299342
|
class EntityGHRepo extends base_Entity {
|
|
299343
|
+
escapeTerraformBranchName(value) {
|
|
299344
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
299345
|
+
}
|
|
298963
299346
|
constructor(artifact) {
|
|
298964
299347
|
super(artifact, {
|
|
298965
299348
|
config: {
|
|
@@ -298968,6 +299351,7 @@ class EntityGHRepo extends base_Entity {
|
|
|
298968
299351
|
teams: [],
|
|
298969
299352
|
collaborators: [],
|
|
298970
299353
|
labels: [],
|
|
299354
|
+
branch_protections: [],
|
|
298971
299355
|
},
|
|
298972
299356
|
});
|
|
298973
299357
|
}
|
|
@@ -299022,6 +299406,8 @@ class EntityGHRepo extends base_Entity {
|
|
|
299022
299406
|
await provisionDefaultBranch(this);
|
|
299023
299407
|
await provisionCodeowners(this);
|
|
299024
299408
|
await provisionVariables(this);
|
|
299409
|
+
// Add branch protections to config if defined
|
|
299410
|
+
provisionBranchProtections(this);
|
|
299025
299411
|
await provisionOIDCSubjectClaim(this);
|
|
299026
299412
|
await provisionPermissions(this);
|
|
299027
299413
|
await provisionLabels(this, repoAlreadyExists);
|
|
@@ -299071,6 +299457,21 @@ class EntityGHRepo extends base_Entity {
|
|
|
299071
299457
|
id: this.cr.name,
|
|
299072
299458
|
},
|
|
299073
299459
|
});
|
|
299460
|
+
// Import github_branch_protection ONLY for protections declared in CR, not GitHub state
|
|
299461
|
+
const protections = this.cr.spec.branchProtections;
|
|
299462
|
+
if (protections && Array.isArray(protections) && protections.length > 0) {
|
|
299463
|
+
for (const protection of protections) {
|
|
299464
|
+
const branch = protection.branch;
|
|
299465
|
+
this.patchImportData({
|
|
299466
|
+
op: PatchOperations.add,
|
|
299467
|
+
path: '/imports/-',
|
|
299468
|
+
value: {
|
|
299469
|
+
to: `github_branch_protection.this["${this.escapeTerraformBranchName(branch)}"]`,
|
|
299470
|
+
id: `${this.cr.name}:${this.escapeTerraformBranchName(branch)}`,
|
|
299471
|
+
},
|
|
299472
|
+
});
|
|
299473
|
+
}
|
|
299474
|
+
}
|
|
299074
299475
|
// we cannot ensure the files exist, thus we need to check
|
|
299075
299476
|
// it they exist before importing them
|
|
299076
299477
|
//for (const file of this.document.config.files) {
|
|
@@ -299093,6 +299494,19 @@ class EntityGHRepo extends base_Entity {
|
|
|
299093
299494
|
},
|
|
299094
299495
|
});
|
|
299095
299496
|
}
|
|
299497
|
+
// Import Pages resource if configured.
|
|
299498
|
+
// When a repository is manually created with Pages already enabled,
|
|
299499
|
+
// we must import the existing resource to avoid a 409 conflict on apply.
|
|
299500
|
+
if (this.cr.spec.pages) {
|
|
299501
|
+
this.patchImportData({
|
|
299502
|
+
op: PatchOperations.add,
|
|
299503
|
+
path: '/imports/-',
|
|
299504
|
+
value: {
|
|
299505
|
+
to: 'github_repository_pages.this[0]',
|
|
299506
|
+
id: this.cr.name,
|
|
299507
|
+
},
|
|
299508
|
+
});
|
|
299509
|
+
}
|
|
299096
299510
|
}
|
|
299097
299511
|
async provisionRepository() {
|
|
299098
299512
|
this.patchData({
|
|
@@ -301739,9 +302153,9 @@ const crs_analyzerSubcommand = {
|
|
|
301739
302153
|
};
|
|
301740
302154
|
|
|
301741
302155
|
;// CONCATENATED MODULE: ./package.json
|
|
301742
|
-
const package_namespaceObject =
|
|
302156
|
+
const package_namespaceObject = {"i8":"2.5.0"};
|
|
301743
302157
|
;// CONCATENATED MODULE: ../../package.json
|
|
301744
|
-
const package_namespaceObject_1 = {"i8":"2.
|
|
302158
|
+
const package_namespaceObject_1 = {"i8":"2.5.0"};
|
|
301745
302159
|
;// CONCATENATED MODULE: ./src/subcommands/index.ts
|
|
301746
302160
|
|
|
301747
302161
|
|
|
@@ -104,6 +104,15 @@ declare const _default: {
|
|
|
104
104
|
tokenizer: {
|
|
105
105
|
SimpleTokenizer: typeof import("./src/tokenizer").SimpleTokenizer;
|
|
106
106
|
};
|
|
107
|
+
codeowners: {
|
|
108
|
+
parse: typeof import("./src/codeowners").parse;
|
|
109
|
+
format: typeof import("./src/codeowners").format;
|
|
110
|
+
validate: typeof import("./src/codeowners").validate;
|
|
111
|
+
updateRule: typeof import("./src/codeowners").updateRule;
|
|
112
|
+
remove: typeof import("./src/codeowners").remove;
|
|
113
|
+
getOwners: typeof import("./src/codeowners").getOwners;
|
|
114
|
+
resolveRefs: typeof import("./src/codeowners").resolveRefs;
|
|
115
|
+
};
|
|
107
116
|
cron: {
|
|
108
117
|
validateCron: typeof validateCron;
|
|
109
118
|
isValidCron: typeof isValidCron;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface CodeownersEntry {
|
|
2
|
+
type: 'rule' | 'comment' | 'blank';
|
|
3
|
+
pattern?: string;
|
|
4
|
+
owners?: string[];
|
|
5
|
+
raw: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parse(raw: string): CodeownersEntry[];
|
|
8
|
+
export declare function format(entries: CodeownersEntry[]): string;
|
|
9
|
+
export declare function validate(raw: string): {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
errors: string[];
|
|
12
|
+
};
|
|
13
|
+
export declare function updateRule(entries: CodeownersEntry[], pattern: string, owners: string[]): CodeownersEntry[];
|
|
14
|
+
export declare function remove(entries: CodeownersEntry[], pattern: string): CodeownersEntry[];
|
|
15
|
+
export declare function getOwners(entries: CodeownersEntry[]): string[];
|
|
16
|
+
export declare function resolveRefs(raw: string, replacements: Record<string, string>): string;
|
|
17
|
+
declare const _default: {
|
|
18
|
+
parse: typeof parse;
|
|
19
|
+
format: typeof format;
|
|
20
|
+
validate: typeof validate;
|
|
21
|
+
updateRule: typeof updateRule;
|
|
22
|
+
remove: typeof remove;
|
|
23
|
+
getOwners: typeof getOwners;
|
|
24
|
+
resolveRefs: typeof resolveRefs;
|
|
25
|
+
};
|
|
26
|
+
export default _default;
|
|
@@ -25,11 +25,7 @@ export declare enum envVars {
|
|
|
25
25
|
s3Bucket = "S3_BUCKET",
|
|
26
26
|
s3Lock = "S3_LOCK",
|
|
27
27
|
s3Region = "S3_REGION",
|
|
28
|
-
cdktfConfigFiles = "CDKTF_CONFIG_FILES",
|
|
29
28
|
exclusionsYamlPath = "EXCLUSIONS_PATH",
|
|
30
|
-
cdktfEntityPath = "FIRESTARTR_CDKTF_ENTITY_PATH",
|
|
31
|
-
cdktfDepsPath = "FIRESTARTR_CDKTF_DEPS_PATH",
|
|
32
|
-
cdktfIsImport = "FIRESTARTR_CDKTF_IS_IMPORT",
|
|
33
29
|
githubAppId = "GITHUB_APP_ID",
|
|
34
30
|
githubAppInstallationId = "GITHUB_APP_INSTALLATION_ID",
|
|
35
31
|
githubAppInstallationIdPrefapp = "GITHUB_APP_INSTALLATION_ID_PREFAPP",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { EntityGHRepo } from '../';
|
|
2
|
+
/**
|
|
3
|
+
* Adds branch protection config to the synthesized Terraform config,
|
|
4
|
+
* preserving legacy field semantics exactly (empty lists, explicit booleans, names).
|
|
5
|
+
* @param entity The EntityGHRepo instance
|
|
6
|
+
*/
|
|
7
|
+
export declare function provisionBranchProtections(entity: EntityGHRepo): void;
|
|
@@ -4,3 +4,4 @@ export { provisionVariables } from './variables';
|
|
|
4
4
|
export { provisionOIDCSubjectClaim } from './actions_oidc';
|
|
5
5
|
export { provisionPermissions } from './teams';
|
|
6
6
|
export { provisionLabels } from './labels';
|
|
7
|
+
export { provisionBranchProtections } from './branch_protections';
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
import type { Dependencies } from './resolver';
|
|
1
2
|
export declare function getKindFromPlural(plural: string): any;
|
|
2
3
|
export declare function getPluralFromKind(kind: string): string;
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Helper to extract the canonical identity (metadata.uid) from a WorkItem or its item.
|
|
6
|
+
*
|
|
7
|
+
* Use this helper when you need a stable identity key for WorkItems, e.g. for
|
|
8
|
+
* in-memory deferred-scheduling bookkeeping.
|
|
9
|
+
*
|
|
10
|
+
* Note: other subsystems may still use kind/namespace/name keys; this helper is
|
|
11
|
+
* specifically intended for UID-based WorkItem identity.
|
|
12
|
+
* Any future change to identity logic MUST be made here and nowhere else.
|
|
13
|
+
*
|
|
14
|
+
* Returns undefined if .metadata.uid is missing so scheduler/bookkeeping paths
|
|
15
|
+
* can safely short-circuit instead of throwing and aborting an entire pass.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getWorkItemIdentity(wi: WorkItem): string | undefined;
|
|
4
18
|
export declare enum OperationType {
|
|
5
19
|
RENAMED = "RENAMED",
|
|
6
20
|
UPDATED = "UPDATED",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { WorkItem } from './definitions';
|
|
2
|
+
/**
|
|
3
|
+
* Maximum number of blocked attempts allowed before an item is moved to the deferred segment.
|
|
4
|
+
* An item becomes deferred once its blocked-attempt count exceeds this value.
|
|
5
|
+
*/
|
|
6
|
+
export declare const MAX_BLOCKED_ATTEMPTS_BEFORE_DEFER = 5;
|
|
7
|
+
export declare const SUPPORTED_PARENT_KINDS: Set<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Encapsulates all deferred-scheduling bookkeeping state for one scheduler queue.
|
|
10
|
+
* Instantiate once per queue (alongside processItemSlotsManager) inside loop().
|
|
11
|
+
*/
|
|
12
|
+
export declare class DeferredSchedulingBookkeeper {
|
|
13
|
+
private _blockedAttempts;
|
|
14
|
+
private _deferredEntryOrder;
|
|
15
|
+
private _deferredOrderCounter;
|
|
16
|
+
/**
|
|
17
|
+
* Returns true only for supported parent kinds AND only for deletion operations.
|
|
18
|
+
* Per spec Definition 2: applicable when operation is MARKED_TO_DELETION,
|
|
19
|
+
* or RETRY with metadata.deletionTimestamp set.
|
|
20
|
+
*/
|
|
21
|
+
isDeferredSchedulingApplicable(item: WorkItem): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Increments block counter for item (only call when item has been detected as blocked).
|
|
24
|
+
* Returns new value. Promotes item to deferred segment if threshold crossed.
|
|
25
|
+
*/
|
|
26
|
+
incrementBlockedAttempt(item: WorkItem): number;
|
|
27
|
+
getBlockedAttempts(item: WorkItem): number;
|
|
28
|
+
isDeferred(item: WorkItem): boolean;
|
|
29
|
+
getDeferredOrder(item: WorkItem): number | undefined;
|
|
30
|
+
removeBookkeeping(item: WorkItem): void;
|
|
31
|
+
/**
|
|
32
|
+
* Splits queue into non-deferred and deferred, preserving original order,
|
|
33
|
+
* but sorts deferred by stable insertion order.
|
|
34
|
+
*/
|
|
35
|
+
partitionQueueByDeferred<T extends WorkItem>(queue: T[]): {
|
|
36
|
+
nonDeferred: T[];
|
|
37
|
+
deferred: T[];
|
|
38
|
+
};
|
|
39
|
+
clearAllBookkeeping(): void;
|
|
40
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WorkItem } from './informer';
|
|
2
|
+
import { DeferredSchedulingBookkeeper } from './processItem.blocks';
|
|
2
3
|
export declare function getQueueMetrics(): {
|
|
3
4
|
nItems: number;
|
|
4
5
|
nItemsFinished: number;
|
|
@@ -25,4 +26,4 @@ export declare function processItem(workItem: WorkItem): Promise<void>;
|
|
|
25
26
|
* Periodically checks the queue for finished WorkItems and removes them
|
|
26
27
|
* @param {WorkItem[]} queue - Queue of WorkItems
|
|
27
28
|
*/
|
|
28
|
-
export declare function workItemGarbageCollector(queue: WorkItem[]): Promise<void>;
|
|
29
|
+
export declare function workItemGarbageCollector(queue: WorkItem[], bookkeeper?: DeferredSchedulingBookkeeper): Promise<void>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { WorkItem } from './definitions';
|
|
2
2
|
import { ProcessItemSlotMetrics } from './metrics/processItem.slot.metrics';
|
|
3
|
-
export declare function initProcessItemsSlots(nSlots: number, initMetrics?: boolean): ProcessItemSlots;
|
|
3
|
+
export declare function initProcessItemsSlots(nSlots: number, initMetrics?: boolean, onWorkItemBlockedByHandler?: (workItem: WorkItem) => void): ProcessItemSlots;
|
|
4
4
|
export declare class ProcessItemSlots {
|
|
5
5
|
_slots: ProcessItemSlot[];
|
|
6
6
|
_nSlots: number;
|
|
7
7
|
_guardRails: GuardRails;
|
|
8
|
-
constructor(nSlots: number, initMetrics: boolean);
|
|
8
|
+
constructor(nSlots: number, initMetrics: boolean, onWorkItemBlockedByHandler?: (workItem: WorkItem) => void);
|
|
9
9
|
getIdleSlots(): Generator<ProcessItemSlot, void, unknown>;
|
|
10
10
|
allSlotsAreIdle(): boolean;
|
|
11
11
|
isWorkItemBlocked(workItem: WorkItem): boolean;
|
|
@@ -24,16 +24,19 @@ export declare class ProcessItemSlot {
|
|
|
24
24
|
_actions: {
|
|
25
25
|
blockItem: (item: any) => void;
|
|
26
26
|
unblockItem: (item: any) => void;
|
|
27
|
+
onWorkItemBlockedByHandler?: (workItem: WorkItem) => void;
|
|
27
28
|
} | undefined;
|
|
28
29
|
_metrics: ProcessItemSlotMetrics;
|
|
29
30
|
constructor(id: number);
|
|
30
31
|
set actions(actions: {
|
|
31
32
|
blockItem: (item: any) => void;
|
|
32
33
|
unblockItem: (item: any) => void;
|
|
34
|
+
onWorkItemBlockedByHandler?: (workItem: WorkItem) => void;
|
|
33
35
|
});
|
|
34
36
|
get actions(): {
|
|
35
37
|
blockItem: (item: any) => void;
|
|
36
38
|
unblockItem: (item: any) => void;
|
|
39
|
+
onWorkItemBlockedByHandler?: (workItem: WorkItem) => void;
|
|
37
40
|
};
|
|
38
41
|
get idle(): boolean;
|
|
39
42
|
get id(): number;
|