@firestartr/cli 2.8.0-snapshot-variant-cloning-test-1 → 2.8.0-snapshot-variant-cloning-1

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/build/index.js CHANGED
@@ -320729,10 +320729,6 @@ const GithubSchemas = [
320729
320729
  description: 'Override fields for a variant of a TFWorkspaceClaim',
320730
320730
  type: 'object',
320731
320731
  properties: {
320732
- name: {
320733
- type: 'string',
320734
- description: 'override the terraform provider name',
320735
- },
320736
320732
  values: {
320737
320733
  type: 'object',
320738
320734
  additionalProperties: true,
@@ -320799,7 +320795,8 @@ const GithubSchemas = [
320799
320795
  properties: {
320800
320796
  name: {
320801
320797
  type: 'string',
320802
- description: 'name of the variant — used as claim name and CR name base',
320798
+ maxLength: 10,
320799
+ description: 'shorthand suffix — composed with main tf name to form the final CR name',
320803
320800
  },
320804
320801
  overrides: {
320805
320802
  $ref: 'firestartr.dev://terraform/TerraformProviderVariantOverride',
@@ -321333,20 +321330,19 @@ async function loadClaim(claimRef, org, defaults = loadClaimDefaults(), patchCla
321333
321330
  throw new Error(`Variant '${variant.name}' in claim ${claimRef} cannot override '${field}'`);
321334
321331
  }
321335
321332
  }
321333
+ const composedName = claim.providers.terraform.name + '-' + variant.name;
321336
321334
  const variantClaim = lodash_default().cloneDeep(claim);
321337
- variantClaim.name = variant.name;
321335
+ variantClaim.name = composedName;
321338
321336
  variantClaim.providers.terraform = lodash_default().merge({}, claim.providers.terraform, variant.overrides);
321339
- if (!variant.overrides.name) {
321340
- variantClaim.providers.terraform.name = variant.name;
321341
- }
321337
+ variantClaim.providers.terraform.name = composedName;
321342
321338
  if (!variantClaim.annotations) {
321343
321339
  variantClaim.annotations = {};
321344
321340
  }
321345
321341
  variantClaim.annotations['firestartr.dev/variant-of'] =
321346
321342
  claim.providers.terraform.name;
321347
- const variantRef = `TFWorkspaceClaim-${variant.name}`;
321343
+ const variantRef = `TFWorkspaceClaim-${composedName}`;
321348
321344
  if (result[variantRef]) {
321349
- throw new Error(`Variant name '${variant.name}' from claim '${claimRef}' conflicts with existing claim '${variantRef}'`);
321345
+ throw new Error(`Variant name '${variant.name}' (composed: '${composedName}') from claim '${claimRef}' conflicts with existing claim '${variantRef}'`);
321350
321346
  }
321351
321347
  logger.info(`Creating synthetic variant claim ${variantRef}`);
321352
321348
  result[variantRef] = {};
@@ -331003,7 +330999,19 @@ var OperationType;
331003
330999
  OperationType["MARKED_TO_DELETION"] = "MARKED_TO_DELETION";
331004
331000
  OperationType["NOTHING"] = "NOTHING";
331005
331001
  OperationType["RETRY"] = "RETRY";
331002
+ OperationType["RETRY_SYNC"] = "RETRY_SYNC";
331006
331003
  })(OperationType || (OperationType = {}));
331004
+ function retryOpForReason(reason) {
331005
+ if (!reason)
331006
+ return OperationType.RETRY;
331007
+ const base = reason.startsWith('RETRY_') ? reason.slice(6) : reason;
331008
+ switch (base) {
331009
+ case 'SYNC':
331010
+ return OperationType.RETRY_SYNC;
331011
+ default:
331012
+ return OperationType.RETRY;
331013
+ }
331014
+ }
331007
331015
  var WorkStatus;
331008
331016
  (function (WorkStatus) {
331009
331017
  WorkStatus["PENDING"] = "PENDING";
@@ -331020,6 +331028,7 @@ const TIMEOUTS = {
331020
331028
  UPDATED: DAY_SECONDS,
331021
331029
  CREATED: DAY_SECONDS,
331022
331030
  RETRY: 120 * 60,
331031
+ RETRY_SYNC: 120 * 60,
331023
331032
  MARKED_TO_DELETION: DAY_SECONDS,
331024
331033
  SYNC: DAY_SECONDS,
331025
331034
  NOTHING: 10,
@@ -331257,13 +331266,13 @@ async function ctl_getItemByItemPath(itemPath, apiGroup = catalog_common/* defau
331257
331266
  const r = await fetchItemByItemPathResponse(itemPath, apiGroup, apiVersion);
331258
331267
  if (!r.ok) {
331259
331268
  const err = new Error(`Error on getItemByItemPath: ${itemPath}: ${r.statusText}`);
331260
- operator_src_logger.error(`Error on getItemByItemPath: ${itemPath}: ${r.statusText}`);
331269
+ operator_src_logger.warn(`Error on getItemByItemPath: ${itemPath}: ${r.statusText}`);
331261
331270
  throw err;
331262
331271
  }
331263
331272
  return await r.json();
331264
331273
  }
331265
331274
  catch (e) {
331266
- operator_src_logger.error(`Error on getItemByItemPath: ${e}`);
331275
+ operator_src_logger.warn(`Error on getItemByItemPath: ${e instanceof Error ? e.stack || e.message : JSON.stringify(e)}`);
331267
331276
  throw e;
331268
331277
  }
331269
331278
  });
@@ -331284,13 +331293,13 @@ async function getItemByItemPathOrNull(itemPath, apiGroup = catalog_common/* def
331284
331293
  }
331285
331294
  if (!r.ok) {
331286
331295
  const err = new Error(`Error on getItemByItemPathOrNull: ${itemPath}: ${r.statusText}`);
331287
- operator_src_logger.error(`Error on getItemByItemPathOrNull: ${itemPath}: ${r.statusText}`);
331296
+ operator_src_logger.warn(`Error on getItemByItemPathOrNull: ${itemPath}: ${r.statusText}`);
331288
331297
  throw err;
331289
331298
  }
331290
331299
  return await r.json();
331291
331300
  }
331292
331301
  catch (e) {
331293
- operator_src_logger.error(`Error on getItemByItemPathOrNull: ${e}`);
331302
+ operator_src_logger.warn(`Error on getItemByItemPathOrNull: ${e instanceof Error ? e.stack || e.message : JSON.stringify(e)}`);
331294
331303
  throw e;
331295
331304
  }
331296
331305
  });
@@ -331456,6 +331465,21 @@ async function upsertResult(namespace, item, result, exitCode = 0, existingRetry
331456
331465
  headers: opts.headers,
331457
331466
  body: JSON.stringify(resultObject),
331458
331467
  });
331468
+ if (r.status === 409) {
331469
+ operator_src_logger.debug(`TFResult '${tfResultName}' already exists (409), updating.`);
331470
+ const existingUrl = `${kc.getCurrentCluster().server}/apis/firestartr.dev/v1/namespaces/${namespace}/tfresults/${tfResultName}`;
331471
+ const getResp = await fetch(existingUrl, { ...opts, method: 'GET' });
331472
+ if (!getResp.ok) {
331473
+ throw `TFResult: failed to fetch existing '${tfResultName}': ${getResp.statusText}`;
331474
+ }
331475
+ const existing = await getResp.json();
331476
+ existing.spec.result = result.toString();
331477
+ const updated = await writeManifest('tfresults', namespace, existing, `apis/firestartr.dev/v1/namespaces/${namespace}/tfresults/${tfResultName}`);
331478
+ await tryWriteTFResultStatus(namespace, tfResultName, updated.metadata.resourceVersion, exitCode, newRetryCount);
331479
+ updated.status = updated.status || {};
331480
+ updated.status.retryCount = newRetryCount;
331481
+ return updated;
331482
+ }
331459
331483
  if (!r.ok) {
331460
331484
  throw `TFResult: ${r.statusText}`;
331461
331485
  }
@@ -332471,15 +332495,20 @@ async function syncer(enqueue) {
332471
332495
  async function loop(enqueueIfNeeded, api) {
332472
332496
  void loopKeeper(api);
332473
332497
  while (1) {
332474
- await fWait();
332475
- const needRevisionItems = Object.values(syncWatchers).filter((watcher) => watcher.needsRevision);
332476
- for (const watcher of needRevisionItems) {
332477
- const item = await getItemIfNeededSync(watcher);
332478
- operator_src_logger.debug(`Item needs revision ${watcher.itemPath}`);
332479
- if (item !== null) {
332480
- enqueueIfNeeded(item);
332498
+ try {
332499
+ await fWait();
332500
+ const needRevisionItems = Object.values(syncWatchers).filter((watcher) => watcher.needsRevision);
332501
+ for (const watcher of needRevisionItems) {
332502
+ const item = await getItemIfNeededSync(watcher);
332503
+ operator_src_logger.debug(`Item needs revision ${watcher.itemPath}`);
332504
+ if (item !== null) {
332505
+ enqueueIfNeeded(item);
332506
+ }
332507
+ watcher.needsRevision = false;
332481
332508
  }
332482
- watcher.needsRevision = false;
332509
+ }
332510
+ catch (err) {
332511
+ operator_src_logger.error(`Error in sync loop: ${err}`);
332483
332512
  }
332484
332513
  }
332485
332514
  }
@@ -332499,15 +332528,34 @@ async function loopKeeper(api) {
332499
332528
  process.exit(1);
332500
332529
  }
332501
332530
  }
332502
- async function getItemIfNeededSync(watcher) {
332503
- const item = await ctl_getItemByItemPath(watcher.itemPath);
332504
- const isProvisioning = item.status?.conditions?.find((condition) => condition.type === 'PROVISIONING' && condition.status === 'True');
332505
- if (isProvisioning)
332506
- return null;
332507
- const isDeleting = item.status?.conditions?.find((condition) => condition.type === 'DELETING' && condition.status === 'True');
332508
- if (isDeleting)
332531
+ async function getItemIfNeededSync(watcher, watchers) {
332532
+ const targetWatchers = watchers ?? syncWatchers;
332533
+ try {
332534
+ const item = await ctl_getItemByItemPath(watcher.itemPath);
332535
+ const isProvisioning = item.status?.conditions?.find((condition) => condition.type === 'PROVISIONING' && condition.status === 'True');
332536
+ if (isProvisioning)
332537
+ return null;
332538
+ const isDeleting = item.status?.conditions?.find((condition) => condition.type === 'DELETING' && condition.status === 'True');
332539
+ if (isDeleting)
332540
+ return null;
332541
+ return item;
332542
+ }
332543
+ catch (e) {
332544
+ if (e.message && e.message.includes('Error on getItemByItemPath')) {
332545
+ if (e.message.includes('Not Found')) {
332546
+ operator_src_logger.debug(`Item '${watcher.itemPath}' not found, removing from sync watchers.`);
332547
+ delete targetWatchers[watcher.itemPath];
332548
+ }
332549
+ else {
332550
+ operator_src_logger.warn(`Transient error fetching '${watcher.itemPath}', keeping sync watcher: ${e.message}`);
332551
+ }
332552
+ return null;
332553
+ }
332554
+ else {
332555
+ operator_src_logger.error(e);
332556
+ }
332509
332557
  return null;
332510
- return item;
332558
+ }
332511
332559
  }
332512
332560
  function fWait(segs = 1) {
332513
332561
  return new Promise((ok) => {
@@ -332515,19 +332563,122 @@ function fWait(segs = 1) {
332515
332563
  });
332516
332564
  }
332517
332565
 
332566
+ ;// CONCATENATED MODULE: ../operator/src/retry.debug.ts
332567
+
332568
+
332569
+
332570
+
332571
+ const RETRY_DEBUG_INTERVAL_MS = 60 * 1000;
332572
+ const loggedEvents = new Set();
332573
+ function writeEventIfNew(itemPath, counter, next, maxRetry = 5) {
332574
+ const key = `${itemPath}-retry-${counter}`;
332575
+ if (loggedEvents.has(key))
332576
+ return;
332577
+ loggedEvents.add(key);
332578
+ resolveRetryType(itemPath)
332579
+ .then((type) => {
332580
+ const nextStr = next ?? 'none';
332581
+ const line = `${itemPath} - ${type} - retry ${counter + 1}/${maxRetry} - next: ${nextStr}\n`;
332582
+ return new Promise((ok, ko) => {
332583
+ external_fs_.appendFile('/tmp/retries', line, (err) => {
332584
+ if (err)
332585
+ ko(`Error appending to /tmp/retries: ${err}`);
332586
+ else
332587
+ ok();
332588
+ });
332589
+ });
332590
+ })
332591
+ .catch(() => {
332592
+ operator_src_logger.warn(`Failed to resolve retry type for ${itemPath}`);
332593
+ });
332594
+ }
332595
+ function initRetryDebug(retryWatchers, maxRetry) {
332596
+ loggedEvents.clear();
332597
+ try {
332598
+ external_fs_.writeFileSync('/tmp/retries', '');
332599
+ }
332600
+ catch {
332601
+ operator_src_logger.warn('Failed to clear /tmp/retries');
332602
+ }
332603
+ let running = false;
332604
+ setInterval(() => {
332605
+ if (running)
332606
+ return;
332607
+ running = true;
332608
+ writeRetryDebug(retryWatchers, maxRetry)
332609
+ .then(() => {
332610
+ running = false;
332611
+ })
332612
+ .catch((err) => {
332613
+ operator_src_logger.error(`PANIC cannot evaluate the retry debug!!!: ${err}`);
332614
+ running = false;
332615
+ });
332616
+ }, RETRY_DEBUG_INTERVAL_MS);
332617
+ }
332618
+ async function writeRetryDebug(retryWatchers, maxRetry) {
332619
+ let output = '';
332620
+ for (const watcher of Object.values(retryWatchers)) {
332621
+ const type = await resolveRetryType(watcher.itemPath);
332622
+ const nextStr = watcher.retry
332623
+ ? 'pending'
332624
+ : (watcher.nextRetryAt ?? 'unknown');
332625
+ output += `${watcher.itemPath} - ${type} - retry ${watcher.retryCounter + 1}/${maxRetry} - next: ${nextStr}\n`;
332626
+ }
332627
+ return new Promise((ok, ko) => {
332628
+ external_fs_.writeFile('/tmp/retries', output, (err) => {
332629
+ if (err)
332630
+ ko(`Error writing /tmp/retries: ${err}`);
332631
+ else
332632
+ ok();
332633
+ });
332634
+ });
332635
+ }
332636
+ async function resolveRetryType(itemPath) {
332637
+ try {
332638
+ const item = await ctl_getItemByItemPath(itemPath);
332639
+ const errorCondition = item.status?.conditions?.find((c) => c.type === 'ERROR' && c.status === 'True');
332640
+ return retryOpForReason(errorCondition?.reason) || 'RETRY';
332641
+ }
332642
+ catch {
332643
+ return 'RETRY';
332644
+ }
332645
+ }
332646
+
332518
332647
  ;// CONCATENATED MODULE: ../operator/src/retry.ts
332519
332648
 
332520
332649
 
332650
+
332521
332651
  // Retries now use exponential backoff, starting at 5 minutes
332522
332652
  // and doubling each time, up to the maximum number of retries
332523
- // configured by MAXRETRY.
332524
- const NEXT_RETRY_MS = 1000 * 60 * 5;
332525
- const MAXRETRY = 5;
332653
+ // configured by OPERATOR_MAX_RETRY (getMaxRetry()).
332654
+ //
332655
+ // Both values can be overridden via environment variables for testing:
332656
+ // OPERATOR_NEXT_RETRY_MS — base delay in ms (default: 300000 = 5 min)
332657
+ // OPERATOR_MAX_RETRY — max retry attempts (default: 5)
332658
+ function getNextRetryMs() {
332659
+ const env = process.env.OPERATOR_NEXT_RETRY_MS;
332660
+ if (env) {
332661
+ const n = Number.parseInt(env, 10);
332662
+ if (Number.isFinite(n) && n > 0)
332663
+ return n;
332664
+ }
332665
+ return 1000 * 60 * 5;
332666
+ }
332667
+ function getMaxRetry() {
332668
+ const env = process.env.OPERATOR_MAX_RETRY;
332669
+ if (env) {
332670
+ const n = Number.parseInt(env, 10);
332671
+ if (Number.isFinite(n) && n > 0)
332672
+ return n;
332673
+ }
332674
+ return 5;
332675
+ }
332526
332676
  function nextRetryMs(currCounter) {
332527
- return NEXT_RETRY_MS * Math.pow(2, currCounter);
332677
+ return getNextRetryMs() * Math.pow(2, currCounter);
332528
332678
  }
332529
332679
  const retryWatchers = {};
332530
332680
  async function initRetry(enqueue) {
332681
+ initRetryDebug(retryWatchers, getMaxRetry());
332531
332682
  void retry_loop(enqueue);
332532
332683
  return {
332533
332684
  errorReconciling(itemPath) {
@@ -332551,23 +332702,29 @@ function retry(itemPath) {
332551
332702
  retry: false,
332552
332703
  retryCounter: 0,
332553
332704
  nextRetry: null,
332705
+ nextRetryAt: null,
332554
332706
  };
332555
332707
  }
332556
332708
  const currCounter = retryWatchers[itemPath].retryCounter;
332557
- const delayMs = NEXT_RETRY_MS * Math.pow(2, currCounter);
332558
- if (currCounter >= MAXRETRY) {
332559
- operator_src_logger.debug(`Failed to process item '${itemPath}'. Retry limit (${MAXRETRY}) reached. No further retries will be attempted.`);
332709
+ const maxRetry = getMaxRetry();
332710
+ const delayMs = nextRetryMs(currCounter);
332711
+ if (currCounter >= maxRetry) {
332712
+ writeEventIfNew(itemPath, currCounter, null, maxRetry);
332713
+ operator_src_logger.debug(`Failed to process item '${itemPath}'. Retry limit (${maxRetry}) reached. No further retries will be attempted.`);
332560
332714
  removeFromRetry(itemPath);
332561
332715
  return;
332562
332716
  }
332563
- operator_src_logger.debug(`Failed to process item '${itemPath}'. Retrying in '${delayMs / 1000 / 60}' minutes. Remaining retries: '${MAXRETRY - currCounter - 1}'.`);
332717
+ operator_src_logger.debug(`Failed to process item '${itemPath}'. Retrying in '${delayMs / 1000 / 60}' minutes. Remaining retries: '${maxRetry - currCounter - 1}'.`);
332564
332718
  retryWatchers[itemPath].retry = false;
332565
332719
  if (retryWatchers[itemPath].nextRetry) {
332566
332720
  clearTimeout(retryWatchers[itemPath].nextRetry);
332567
332721
  }
332722
+ retryWatchers[itemPath].nextRetryAt = new Date(Date.now() + delayMs).toISOString();
332568
332723
  retryWatchers[itemPath].nextRetry = setTimeout(() => {
332569
- if (itemPath in retryWatchers)
332724
+ if (itemPath in retryWatchers) {
332570
332725
  retryWatchers[itemPath].retry = true;
332726
+ retryWatchers[itemPath].nextRetryAt = null;
332727
+ }
332571
332728
  }, delayMs);
332572
332729
  }
332573
332730
  function removeFromRetry(itemPath) {
@@ -332579,7 +332736,8 @@ function removeFromRetry(itemPath) {
332579
332736
  async function retry_loop(enqueueIfNeeded) {
332580
332737
  while (1) {
332581
332738
  await retry_fWait();
332582
- const needRetryWatchItem = Object.values(retryWatchers).filter((watcher) => watcher.retryCounter < MAXRETRY && watcher.retry);
332739
+ const maxRetry = getMaxRetry();
332740
+ const needRetryWatchItem = Object.values(retryWatchers).filter((watcher) => watcher.retryCounter < maxRetry && watcher.retry);
332583
332741
  for (const watcher of needRetryWatchItem) {
332584
332742
  const item = await getItemIfNeededRetry(watcher);
332585
332743
  if (item !== null) {
@@ -332606,7 +332764,7 @@ async function getItemIfNeededRetry(watcher) {
332606
332764
  return null;
332607
332765
  }
332608
332766
  else {
332609
- console.error(e);
332767
+ operator_src_logger.warn(`Unexpected error in retry check for '${watcher.itemPath}': ${e}`);
332610
332768
  }
332611
332769
  return null;
332612
332770
  }
@@ -332891,7 +333049,7 @@ function enqueue(pluralKind, workItem, queue, compute, syncCtl, retryCtl) {
332891
333049
  const resolvedExitCode = exitCode !== undefined ? exitCode : /\berror\b/i.test(output) ? 1 : 0;
332892
333050
  const retryCount = result?.status?.retryCount ?? 0;
332893
333051
  const backoffCounter = Math.max(retryCount - 1, 0);
332894
- const nextRetryTime = resolvedExitCode === 0 || backoffCounter >= MAXRETRY
333052
+ const nextRetryTime = resolvedExitCode === 0 || backoffCounter >= getMaxRetry()
332895
333053
  ? undefined
332896
333054
  : new Date(Date.now() + nextRetryMs(backoffCounter)).toISOString();
332897
333055
  await updateRetryStatusInCR(pluralKind, item.metadata.namespace, item.metadata.name, retryCount, nextRetryTime);
@@ -332906,6 +333064,7 @@ function enqueue(pluralKind, workItem, queue, compute, syncCtl, retryCtl) {
332906
333064
  itemPath: () => informer_itemPath(pluralKind, workItem.item),
332907
333065
  error: () => retryCtl.errorReconciling(informer_itemPath(pluralKind, workItem.item)),
332908
333066
  success: () => retryCtl.successReconciling(informer_itemPath(pluralKind, workItem.item)),
333067
+ removeFromRetry: () => retryCtl.successReconciling(informer_itemPath(pluralKind, workItem.item)),
332909
333068
  recommendedTimeout: () => {
332910
333069
  const customTimeoutAnnotation = catalog_common/* default.generic.getFirestartrAnnotation */.Z.generic.getFirestartrAnnotation('test-custom-timeout');
332911
333070
  let customTimeout = null;
@@ -332925,7 +333084,8 @@ function enqueue(pluralKind, workItem, queue, compute, syncCtl, retryCtl) {
332925
333084
  }
332926
333085
  // only for delete operations
332927
333086
  if (operation !== OperationType.MARKED_TO_DELETION) {
332928
- if (operation === OperationType.RETRY) {
333087
+ if (operation === OperationType.RETRY ||
333088
+ operation === OperationType.RETRY_SYNC) {
332929
333089
  // is the retry a delete operation?
332930
333090
  if (!('deletionTimestamp' in workItem.item.metadata)) {
332931
333091
  return false;
@@ -332961,13 +333121,15 @@ function enqueue(pluralKind, workItem, queue, compute, syncCtl, retryCtl) {
332961
333121
  operation === OperationType.UPDATED ||
332962
333122
  operation === OperationType.SYNC ||
332963
333123
  operation === OperationType.CREATED ||
332964
- operation === OperationType.RETRY;
333124
+ operation === OperationType.RETRY ||
333125
+ operation === OperationType.RETRY_SYNC;
332965
333126
  await setSyncStatus(workItem.handler.itemPath(), operation, 'False', 'Sync process started');
332966
333127
  for await (const transition of compute(item, operation, handler)) {
332967
333128
  yield transition;
332968
333129
  }
332969
333130
  if (needsUpdateSyncConditions) {
332970
- if (operation === OperationType.SYNC) {
333131
+ if (operation === OperationType.SYNC ||
333132
+ operation === OperationType.RETRY_SYNC) {
332971
333133
  // Fetch the CR once and reuse for both hasSyncFailed check and ERROR guard
332972
333134
  const currentItem = await ctl_getItemByItemPath(workItem.handler.itemPath());
332973
333135
  const syncStatus = await getSyncStatus(workItem.handler.itemPath(), currentItem);
@@ -332982,7 +333144,10 @@ function enqueue(pluralKind, workItem, queue, compute, syncCtl, retryCtl) {
332982
333144
  return;
332983
333145
  }
332984
333146
  }
332985
- await setSyncStatus(workItem.handler.itemPath(), operation, operation === OperationType.SYNC ? 'True' : 'False', 'Sync process finished');
333147
+ await setSyncStatus(workItem.handler.itemPath(), operation, operation === OperationType.SYNC ||
333148
+ operation === OperationType.RETRY_SYNC
333149
+ ? 'True'
333150
+ : 'False', 'Sync process finished');
332986
333151
  void syncCtl.updateItem(informer_itemPath(pluralKind, item));
332987
333152
  }
332988
333153
  else {
@@ -333044,18 +333209,21 @@ async function inform(pluralKind, item, op, lastWorkItem = null) {
333044
333209
  return workItem;
333045
333210
  }
333046
333211
  return null;
333047
- case 'onRetry':
333212
+ case 'onRetry': {
333048
333213
  if (workItem !== null && workItem.workStatus !== WorkStatus.FINISHED) {
333049
333214
  return null;
333050
333215
  }
333216
+ const errorCondition = item.status?.conditions?.find((c) => c.type === 'ERROR' && c.status === 'True');
333217
+ const retryOp = retryOpForReason(errorCondition?.reason);
333051
333218
  workItem = {
333052
- operation: OperationType.RETRY,
333219
+ operation: retryOp,
333053
333220
  item,
333054
333221
  workStatus: WorkStatus.PENDING,
333055
333222
  onDelete: function () { },
333056
333223
  upsertTime: Date.now(),
333057
333224
  };
333058
333225
  return workItem;
333226
+ }
333059
333227
  case 'onAdd':
333060
333228
  needed = await needsProvisioningOnCreateOrUpdate(item);
333061
333229
  if (needed.needs) {
@@ -333637,7 +333805,8 @@ class DeferredSchedulingBookkeeper {
333637
333805
  const op = item.operation;
333638
333806
  if (op === OperationType.MARKED_TO_DELETION)
333639
333807
  return true;
333640
- if (op === OperationType.RETRY && item.item?.metadata?.deletionTimestamp)
333808
+ if ((op === OperationType.RETRY || op === OperationType.RETRY_SYNC) &&
333809
+ item.item?.metadata?.deletionTimestamp)
333641
333810
  return true;
333642
333811
  return false;
333643
333812
  }
@@ -333913,6 +334082,7 @@ async function writeDiagnosticSnapshot(queue, maxSlots) {
333913
334082
  const envTfmMirrorRefreshInterval = process.env.TFM_MIRROR_REFRESH_INTERVAL || '900';
333914
334083
  const envBackendProvider = process.env.BACKEND_PROVIDER_NAME || 'kubernetes-provider';
333915
334084
  const envGithubAppId = process.env.GITHUB_APP_ID || '';
334085
+ const envTfmSkipGitConfig = process.env.TFM_SKIP_GIT_CONFIG || 'false';
333916
334086
  // Build YAML output
333917
334087
  let output = '';
333918
334088
  output += `timestamp: "${now}"\n`;
@@ -333925,6 +334095,7 @@ async function writeDiagnosticSnapshot(queue, maxSlots) {
333925
334095
  output += ` TFM_MIRROR_REFRESH_INTERVAL: ${envTfmMirrorRefreshInterval}\n`;
333926
334096
  output += ` BACKEND_PROVIDER_NAME: ${envBackendProvider}\n`;
333927
334097
  output += ` GITHUB_APP_ID: ${envGithubAppId}\n`;
334098
+ output += ` TFM_SKIP_GIT_CONFIG: ${envTfmSkipGitConfig}\n`;
333928
334099
  output += 'queue:\n';
333929
334100
  output += ` total: ${queue.length}\n`;
333930
334101
  output += ` pending: ${pending}\n`;
@@ -334683,6 +334854,7 @@ const WEIGHTS = {
334683
334854
  UPDATED: 10,
334684
334855
  CREATED: 9,
334685
334856
  RETRY: 8,
334857
+ RETRY_SYNC: 8,
334686
334858
  MARKED_TO_DELETION: 6,
334687
334859
  SYNC: 1,
334688
334860
  NOTHING: 0,
@@ -335689,16 +335861,26 @@ async function gitFetchPrune(mirrorPath) {
335689
335861
  });
335690
335862
  }
335691
335863
  /**
335692
- * Single package-wide GitHub authentication config writer for repo mirrors and remote workflows.
335693
335864
  * Writes the required url rewriting stanza to /home/node/.gitconfig based on the current org's GitHub App token.
335694
335865
  * This is the ONLY allowed implementation of project git-auth per package specs.
335866
+ *
335867
+ * When TFM_SKIP_GIT_CONFIG=true is set, the function removes any existing
335868
+ * /home/node/.gitconfig and returns early without writing a new one. This is
335869
+ * used in dev/test environments where no GitHub App credentials are available
335870
+ * (e.g. local dev, smoke tests using prefapp/tfm mirrors).
335695
335871
  */
335696
335872
  async function configGit() {
335873
+ const gitconfigPath = '/home/node/.gitconfig';
335874
+ if (process.env.TFM_SKIP_GIT_CONFIG === 'true') {
335875
+ if (external_fs_.existsSync(gitconfigPath)) {
335876
+ external_fs_.rmSync(gitconfigPath);
335877
+ }
335878
+ return;
335879
+ }
335697
335880
  // Import github dynamically to avoid Jest ESM issues when not specifically running this function
335698
335881
  // (see CONSTITUTION and RULES: must not change test machinery)
335699
335882
  const githubModule = await Promise.resolve(/* import() */).then(__nccwpck_require__.bind(__nccwpck_require__, 99029));
335700
335883
  const github = githubModule.default || githubModule;
335701
- const gitconfigPath = '/home/node/.gitconfig';
335702
335884
  if (external_fs_.existsSync(gitconfigPath)) {
335703
335885
  external_fs_.rmSync(gitconfigPath);
335704
335886
  }
@@ -337221,8 +337403,8 @@ function policyAllowsOp(policy, op, item) {
337221
337403
  msg = `A RETRY operation is not allowed on a resource with a deletionTimestamp. It means
337222
337404
  that the resource is being deleted and it is not possible to retry the operation with policy: ${policy}.
337223
337405
 
337224
- To fix this issue, you must first delete the "firestatr.dev/finalizer" from finalizers array in the resource manifest,
337225
- it will be automatically deleted by kubernetes. Terraform will not execute any operation on the resource.
337406
+ To fix this issue, you must first delete the "firestartr.dev/finalizer" from finalizers array in the resource manifest,
337407
+ it will be automatically deleted by Kubernetes. Terraform will not execute any operation on the resource.
337226
337408
  `;
337227
337409
  return { allowed: false, msg };
337228
337410
  }
@@ -337230,6 +337412,19 @@ it will be automatically deleted by kubernetes. Terraform will not execute any o
337230
337412
  return { allowed: true, msg };
337231
337413
  }
337232
337414
  }
337415
+ if (op === OperationType.RETRY_SYNC.toString() &&
337416
+ foundPolicy?.allowedOps.includes(OperationType.SYNC.toString())) {
337417
+ if ('deletionTimestamp' in item.metadata && 'full-control' !== policy) {
337418
+ msg = `A RETRY_SYNC operation is not allowed on a resource with a deletionTimestamp. It means
337419
+ that the resource is being deleted and it is not possible to retry the operation with policy: ${policy}.
337420
+
337421
+ To fix this issue, you must first delete the "firestartr.dev/finalizer" from finalizers array in the resource manifest,
337422
+ it will be automatically deleted by Kubernetes. Terraform will not execute any operation on the resource.
337423
+ `;
337424
+ return { allowed: false, msg };
337425
+ }
337426
+ return { allowed: true, msg };
337427
+ }
337233
337428
  if (!foundPolicy) {
337234
337429
  msg = `Policy ${policy} not found`;
337235
337430
  return { allowed: false, msg };
@@ -337432,6 +337627,7 @@ function processOperation(item, op, handler) {
337432
337627
  case OperationType.MARKED_TO_DELETION:
337433
337628
  return process_operation_markedToDeletion(item, op, handler);
337434
337629
  case OperationType.RETRY:
337630
+ case OperationType.RETRY_SYNC:
337435
337631
  return process_operation_retry(item, op, handler);
337436
337632
  case OperationType.NOTHING:
337437
337633
  return process_operation_nothing(item, op, handler);
@@ -337449,11 +337645,26 @@ async function* process_operation_observe(item, op, handler) {
337449
337645
  yield transition;
337450
337646
  }
337451
337647
  }
337452
- async function* doPlanJSONFormat(item, op, handler, setResult = function (_r) { }) {
337648
+ async function* doPlanJSONFormat(item, op, handler, setResult = function (_r) { }, emitSyncErrorCondition = true) {
337453
337649
  let error = false;
337454
337650
  let deps;
337455
337651
  let terraformExecutionStarted = false;
337456
337652
  const sessionId = generateSessionId();
337653
+ if (op !== OperationType.RETRY && op !== OperationType.RETRY_SYNC) {
337654
+ try {
337655
+ await handler.writeTerraformOutputInTfResult(item, '', 0);
337656
+ }
337657
+ catch (e) {
337658
+ operator_src_logger.warn(`Failed to clear Terraform output: ${e}`);
337659
+ }
337660
+ try {
337661
+ if (handler.removeFromRetry)
337662
+ handler.removeFromRetry();
337663
+ }
337664
+ catch (e) {
337665
+ operator_src_logger.warn(`Failed to clear retry watcher: ${e}`);
337666
+ }
337667
+ }
337457
337668
  try {
337458
337669
  yield {
337459
337670
  item,
@@ -337582,17 +337793,22 @@ async function* doPlanJSONFormat(item, op, handler, setResult = function (_r) {
337582
337793
  }
337583
337794
  }
337584
337795
  if (error) {
337585
- if (op === OperationType.SYNC) {
337586
- // if there is an error on a sync we never put the state on error
337587
- // it would be problematic because the RETRY op kicks in
337588
- if (error) {
337796
+ if (op === OperationType.SYNC || op === OperationType.RETRY_SYNC) {
337797
+ if (emitSyncErrorCondition) {
337589
337798
  yield {
337590
337799
  item,
337591
337800
  reason: op,
337592
- type: 'SYNCHRONIZED',
337801
+ type: 'ERROR',
337593
337802
  status: 'True',
337594
337803
  message: SYNC_DEFAULT_ERROR_MESSAGE,
337595
337804
  };
337805
+ yield {
337806
+ item,
337807
+ reason: op,
337808
+ type: 'SYNCHRONIZED',
337809
+ status: 'False',
337810
+ message: SYNC_DEFAULT_ERROR_MESSAGE,
337811
+ };
337596
337812
  yield {
337597
337813
  item,
337598
337814
  reason: op,
@@ -337614,6 +337830,29 @@ async function* doPlanJSONFormat(item, op, handler, setResult = function (_r) {
337614
337830
  status: 'False',
337615
337831
  message: 'doPlanJSONFormat',
337616
337832
  };
337833
+ }
337834
+ else {
337835
+ yield {
337836
+ item,
337837
+ reason: op,
337838
+ type: 'SYNCHRONIZED',
337839
+ status: 'True',
337840
+ message: SYNC_DEFAULT_ERROR_MESSAGE,
337841
+ };
337842
+ yield {
337843
+ item,
337844
+ reason: op,
337845
+ type: 'PROVISIONED',
337846
+ status: 'True',
337847
+ message: 'doPlanJSONFormat',
337848
+ };
337849
+ yield {
337850
+ item,
337851
+ reason: op,
337852
+ type: 'PLANNING',
337853
+ status: 'False',
337854
+ message: 'doPlanJSONFormat',
337855
+ };
337617
337856
  yield {
337618
337857
  item,
337619
337858
  reason: op,
@@ -337656,9 +337895,16 @@ async function* doPlanJSONFormat(item, op, handler, setResult = function (_r) {
337656
337895
  }
337657
337896
  }
337658
337897
  else {
337659
- if (op === OperationType.SYNC) {
337898
+ handler.success();
337899
+ if (op === OperationType.SYNC || op === OperationType.RETRY_SYNC) {
337660
337900
  setResult('SYNC_SUCCESS');
337661
337901
  }
337902
+ try {
337903
+ await handler.writeTerraformOutputInTfResult(item, '', 0);
337904
+ }
337905
+ catch (e) {
337906
+ operator_src_logger.warn(`Failed to clear Terraform output: ${e}`);
337907
+ }
337662
337908
  }
337663
337909
  }
337664
337910
  }
@@ -337688,6 +337934,13 @@ async function* process_operation_retry(item, op, handler) {
337688
337934
  yield transition;
337689
337935
  }
337690
337936
  }
337937
+ else if (op === OperationType.RETRY_SYNC) {
337938
+ const policy = getPolicy(item, 'firestartr.dev/policy');
337939
+ const syncPolicy = getPolicy(item, 'firestartr.dev/sync-policy');
337940
+ for await (const transition of process_operation_sync(item, OperationType.RETRY_SYNC, handler, syncPolicy, policy)) {
337941
+ yield transition;
337942
+ }
337943
+ }
337691
337944
  else {
337692
337945
  for await (const transition of doApply(item, op, handler)) {
337693
337946
  yield transition;
@@ -337704,7 +337957,7 @@ async function* process_operation_sync(item, op, handler, syncPolicy, generalPol
337704
337957
  let doResult = '';
337705
337958
  if (!syncPolicy) {
337706
337959
  operator_src_logger.debug(`The Terraform processor is only observing item '${item.kind}/${item.metadata.name}' because no sync policy was found for operation '${op}'.`);
337707
- yield* doPlanJSONFormat(item, op, handler);
337960
+ yield* doPlanJSONFormat(item, op, handler, undefined, false);
337708
337961
  return;
337709
337962
  }
337710
337963
  else if (!catalog_common/* default.policies.policiesAreCompatible */.Z.policies.policiesAreCompatible(syncPolicy, generalPolicy)) {
@@ -337749,6 +338002,9 @@ async function* process_operation_markedToDeletion(item, op, handler) {
337749
338002
  let terraformExecutionStarted = false;
337750
338003
  const sessionId = generateSessionId();
337751
338004
  try {
338005
+ if (op === OperationType.MARKED_TO_DELETION) {
338006
+ await handler.writeTerraformOutputInTfResult(item, '', 0);
338007
+ }
337752
338008
  const type = 'DELETING';
337753
338009
  yield {
337754
338010
  item,
@@ -337898,6 +338154,21 @@ async function* doApply(item, op, handler) {
337898
338154
  let deps;
337899
338155
  let terraformExecutionStarted = false;
337900
338156
  const sessionId = generateSessionId();
338157
+ if (op !== OperationType.RETRY && op !== OperationType.RETRY_SYNC) {
338158
+ try {
338159
+ await handler.writeTerraformOutputInTfResult(item, '', 0);
338160
+ }
338161
+ catch (e) {
338162
+ operator_src_logger.warn(`Failed to clear Terraform output: ${e}`);
338163
+ }
338164
+ try {
338165
+ if (handler.removeFromRetry)
338166
+ handler.removeFromRetry();
338167
+ }
338168
+ catch (e) {
338169
+ operator_src_logger.warn(`Failed to clear retry watcher: ${e}`);
338170
+ }
338171
+ }
337901
338172
  try {
337902
338173
  yield {
337903
338174
  item,
@@ -342152,6 +342423,7 @@ function process_operation_processOperation(item, op, handler) {
342152
342423
  case OperationType.MARKED_TO_DELETION:
342153
342424
  return gh_process_operation_markedToDeletion(item, op, handler);
342154
342425
  case OperationType.RETRY:
342426
+ case OperationType.RETRY_SYNC:
342155
342427
  return gh_process_operation_retry(item, op, handler);
342156
342428
  case OperationType.NOTHING:
342157
342429
  return gh_process_operation_nothing(item, op, handler);
@@ -342188,6 +342460,11 @@ async function* gh_process_operation_retry(item, op, handler) {
342188
342460
  yield transition;
342189
342461
  }
342190
342462
  }
342463
+ else if (op === OperationType.RETRY_SYNC) {
342464
+ for await (const transition of gh_process_operation_sync(item, OperationType.RETRY_SYNC, handler)) {
342465
+ yield transition;
342466
+ }
342467
+ }
342191
342468
  else {
342192
342469
  for await (const transition of process_operation_doApply(item, op, handler)) {
342193
342470
  yield transition;
@@ -342340,6 +342617,14 @@ async function* process_operation_doApply(item, op, handler) {
342340
342617
  let checkRunCtl;
342341
342618
  let checkRunTFCtl;
342342
342619
  let error = false;
342620
+ if (op !== OperationType.RETRY && op !== OperationType.RETRY_SYNC) {
342621
+ try {
342622
+ await handler.writeTerraformOutputInTfResult(item, '', 0);
342623
+ }
342624
+ catch (e) {
342625
+ operator_src_logger.warn(`Failed to reset retry count: ${e instanceof Error ? e.stack || e.message : JSON.stringify(e)}`);
342626
+ }
342627
+ }
342343
342628
  try {
342344
342629
  yield {
342345
342630
  item,
@@ -342373,6 +342658,7 @@ async function* process_operation_doApply(item, op, handler) {
342373
342658
  if (op === OperationType.UPDATED ||
342374
342659
  op === OperationType.RENAMED ||
342375
342660
  op === OperationType.RETRY ||
342661
+ op === OperationType.RETRY_SYNC ||
342376
342662
  op === OperationType.SYNC) {
342377
342663
  opts['update'] = true;
342378
342664
  }
@@ -521630,7 +521916,7 @@ const crsStatusSubcommand = {
521630
521916
  };
521631
521917
 
521632
521918
  ;// CONCATENATED MODULE: ./package.json
521633
- const package_namespaceObject = JSON.parse('{"i8":"2.8.0-snapshot-variant-cloning-test-1"}');
521919
+ const package_namespaceObject = JSON.parse('{"i8":"2.8.0-snapshot-variant-cloning-1"}');
521634
521920
  ;// CONCATENATED MODULE: ../../package.json
521635
521921
  const package_namespaceObject_1 = {"i8":"2.7.1"};
521636
521922
  ;// CONCATENATED MODULE: ./src/subcommands/index.ts
@@ -1238,10 +1238,6 @@ declare const schemas: {
1238
1238
  description: string;
1239
1239
  type: string;
1240
1240
  properties: {
1241
- name: {
1242
- type: string;
1243
- description: string;
1244
- };
1245
1241
  values: {
1246
1242
  type: string;
1247
1243
  additionalProperties: boolean;
@@ -1320,6 +1316,7 @@ declare const schemas: {
1320
1316
  properties: {
1321
1317
  name: {
1322
1318
  type: string;
1319
+ maxLength: number;
1323
1320
  description: string;
1324
1321
  };
1325
1322
  overrides: {
@@ -143,10 +143,6 @@ export declare const TerraformSchemas: {
143
143
  description: string;
144
144
  type: string;
145
145
  properties: {
146
- name: {
147
- type: string;
148
- description: string;
149
- };
150
146
  values: {
151
147
  type: string;
152
148
  additionalProperties: boolean;
@@ -225,6 +221,7 @@ export declare const TerraformSchemas: {
225
221
  properties: {
226
222
  name: {
227
223
  type: string;
224
+ maxLength: number;
228
225
  description: string;
229
226
  };
230
227
  overrides: {
@@ -143,10 +143,6 @@ declare const _default: {
143
143
  description: string;
144
144
  type: string;
145
145
  properties: {
146
- name: {
147
- type: string;
148
- description: string;
149
- };
150
146
  values: {
151
147
  type: string;
152
148
  additionalProperties: boolean;
@@ -225,6 +221,7 @@ declare const _default: {
225
221
  properties: {
226
222
  name: {
227
223
  type: string;
224
+ maxLength: number;
228
225
  description: string;
229
226
  };
230
227
  overrides: {
@@ -22,8 +22,10 @@ export declare enum OperationType {
22
22
  SYNC = "SYNC",
23
23
  MARKED_TO_DELETION = "MARKED_TO_DELETION",
24
24
  NOTHING = "NOTHING",
25
- RETRY = "RETRY"
25
+ RETRY = "RETRY",
26
+ RETRY_SYNC = "RETRY_SYNC"
26
27
  }
28
+ export declare function retryOpForReason(reason: string | undefined): OperationType;
27
29
  export declare enum WorkStatus {
28
30
  PENDING = "PENDING",
29
31
  PROCESSING = "PROCESSING",
@@ -65,6 +67,7 @@ export type WorkItemHandler = {
65
67
  itemPath: ItemPathFn;
66
68
  error: ErrorFn;
67
69
  success: SuccessFn;
70
+ removeFromRetry?: () => void;
68
71
  isBlocked?: boolean;
69
72
  isPicked?: boolean;
70
73
  needsBlocking?: (item: any, operation: OperationType) => boolean;
@@ -6,3 +6,17 @@ export declare function processOperation(item: any, op: OperationType, handler:
6
6
  status: string;
7
7
  message: string;
8
8
  }, void, unknown>;
9
+ export declare function retry(item: any, op: OperationType, handler: any): AsyncGenerator<{
10
+ item: any;
11
+ reason: OperationType;
12
+ type: string;
13
+ status: string;
14
+ message: string;
15
+ }, void, unknown>;
16
+ export declare function sync(item: any, op: OperationType, handler: any): AsyncGenerator<{
17
+ item: any;
18
+ reason: OperationType;
19
+ type: string;
20
+ status: string;
21
+ message: string;
22
+ }, void, unknown>;
@@ -1,5 +1,4 @@
1
- export declare const NEXT_RETRY_MS: number;
2
- export declare const MAXRETRY = 5;
1
+ export declare function getMaxRetry(): number;
3
2
  export declare function nextRetryMs(currCounter: number): number;
4
3
  export declare function initRetry(enqueue: Function): Promise<{
5
4
  errorReconciling(itemPath: string): void;
@@ -0,0 +1,11 @@
1
+ type RetryWatcher = {
2
+ itemPath: string;
3
+ retryCounter: number;
4
+ retry: boolean;
5
+ nextRetryAt: string | null;
6
+ };
7
+ export declare function writeEventIfNew(itemPath: string, counter: number, next: string | null, maxRetry?: number): void;
8
+ export declare function initRetryDebug(retryWatchers: {
9
+ [key: string]: RetryWatcher;
10
+ }, maxRetry: number): void;
11
+ export {};
@@ -8,3 +8,6 @@ export declare function syncer(enqueue: Function): Promise<{
8
8
  deleteItem(itemPath: string): void;
9
9
  }>;
10
10
  export declare function loop(enqueueIfNeeded: Function, api: any): Promise<void>;
11
+ export declare function getItemIfNeededSync(watcher: any, watchers?: {
12
+ [key: string]: SyncWatcher;
13
+ }): Promise<any>;
@@ -7,6 +7,27 @@ export declare function processOperation(item: any, op: OperationType, handler:
7
7
  status: string;
8
8
  message: any;
9
9
  }, void, unknown>;
10
+ export declare function doPlanJSONFormat(item: any, op: OperationType, handler: any, setResult?: (_r: string) => void, emitSyncErrorCondition?: boolean): AsyncGenerator<{
11
+ item: any;
12
+ reason: OperationType;
13
+ type: string;
14
+ status: string;
15
+ message: any;
16
+ }, void, unknown>;
17
+ export declare function retry(item: any, op: OperationType, handler: any): AsyncGenerator<{
18
+ item: any;
19
+ reason: OperationType;
20
+ type: string;
21
+ status: string;
22
+ message: any;
23
+ }, void, unknown>;
24
+ export declare function sync(item: any, op: OperationType, handler: any, syncPolicy: string, generalPolicy: string): AsyncGenerator<{
25
+ item: any;
26
+ reason: OperationType;
27
+ type: string;
28
+ status: string;
29
+ message: any;
30
+ }, void, unknown>;
10
31
  /**
11
32
  *
12
33
  * @param {any} item - CR to be applied
@@ -38,8 +38,12 @@ export declare function gitRemoteUpdate(mirrorPath: string): Promise<void>;
38
38
  */
39
39
  export declare function gitFetchPrune(mirrorPath: string): Promise<void>;
40
40
  /**
41
- * Single package-wide GitHub authentication config writer for repo mirrors and remote workflows.
42
41
  * Writes the required url rewriting stanza to /home/node/.gitconfig based on the current org's GitHub App token.
43
42
  * This is the ONLY allowed implementation of project git-auth per package specs.
43
+ *
44
+ * When TFM_SKIP_GIT_CONFIG=true is set, the function removes any existing
45
+ * /home/node/.gitconfig and returns early without writing a new one. This is
46
+ * used in dev/test environments where no GitHub App credentials are available
47
+ * (e.g. local dev, smoke tests using prefapp/tfm mirrors).
44
48
  */
45
49
  export declare function configGit(): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firestartr/cli",
3
- "version": "2.8.0-snapshot-variant-cloning-test-1",
3
+ "version": "2.8.0-snapshot-variant-cloning-1",
4
4
  "private": false,
5
5
  "description": "Commandline tool",
6
6
  "main": "build/main.js",