@firestartr/cli 2.2.2-snapshot-0 → 2.3.0-snapshot

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.
Files changed (24) hide show
  1. package/build/index.js +1036 -481
  2. package/build/packages/catalog_common/src/types/envvars.d.ts +1 -0
  3. package/build/packages/cdk8s_renderer/imports/firestartr.dev.d.ts +33 -0
  4. package/build/packages/cdk8s_renderer/src/claims/base/schemas/index.d.ts +36 -0
  5. package/build/packages/cdk8s_renderer/src/claims/github/component.labels.schema.d.ts +32 -0
  6. package/build/packages/cdk8s_renderer/src/claims/github/component.schema.d.ts +6 -0
  7. package/build/packages/cdk8s_renderer/src/claims/github/index.d.ts +36 -0
  8. package/build/packages/cdk8s_renderer/src/claims/github/repository.d.ts +6 -0
  9. package/build/packages/cdk8s_renderer/src/initializers/component_labels.d.ts +11 -0
  10. package/build/packages/gh_provisioner/src/entities/ghrepo/helpers/index.d.ts +1 -0
  11. package/build/packages/gh_provisioner/src/entities/ghrepo/helpers/labels.d.ts +1 -0
  12. package/build/packages/github/index.d.ts +2 -0
  13. package/build/packages/github/src/repository.d.ts +8 -0
  14. package/build/packages/operator/src/definitions.d.ts +59 -0
  15. package/build/packages/operator/src/informer.d.ts +4 -55
  16. package/build/packages/operator/src/metrics/processItem.global.metrics.d.ts +12 -0
  17. package/build/packages/operator/src/metrics/processItem.slot.metrics.d.ts +15 -0
  18. package/build/packages/operator/src/metricsServer.d.ts +1 -0
  19. package/build/packages/operator/src/processItem.slot.d.ts +45 -0
  20. package/build/packages/operator/src/signals.d.ts +1 -17
  21. package/build/packages/terraform_provisioner/src/process_handler.d.ts +1 -0
  22. package/build/packages/terraform_provisioner/src/project_tf.d.ts +0 -1
  23. package/build/packages/terraform_provisioner/src/utils.d.ts +2 -2
  24. package/package.json +1 -1
package/build/index.js CHANGED
@@ -276790,6 +276790,7 @@ var envVars;
276790
276790
  envVars["operatorDummyExec"] = "OPERATOR_DUMMY_EXEC";
276791
276791
  envVars["operatorIgnoreLease"] = "OPERATOR_IGNORE_LEASE";
276792
276792
  envVars["operatorDeploymentName"] = "OPERATOR_DEPLOYMENT_NAME";
276793
+ envVars["operatorNumberOfMaxSlots"] = "OPERATOR_NUMBER_OF_MAX_SLOTS";
276793
276794
  // ---- KUBERNETES VARIABLES -----------------------------------------------
276794
276795
  envVars["kubernetesServiceHost"] = "KUBERNETES_SERVICE_HOST";
276795
276796
  envVars["kubernetesServicePort"] = "KUBERNETES_SERVICE_PORT";
@@ -284668,6 +284669,16 @@ async function getRepoInfo(owner, name) {
284668
284669
  const res = await octokit.repos.get({ owner: owner, repo: name });
284669
284670
  return res['data'];
284670
284671
  }
284672
+ async function repoExists(owner, name) {
284673
+ github_src_logger.info(`Checking if repo exists: ${owner}/${name}`);
284674
+ try {
284675
+ await getRepoInfo(owner, name);
284676
+ return true;
284677
+ }
284678
+ catch (error) {
284679
+ return false;
284680
+ }
284681
+ }
284671
284682
  async function getPages(owner, name) {
284672
284683
  github_src_logger.info(`Getting pages for ${owner}/${name}`);
284673
284684
  const octokit = await getOctokitForOrg(owner);
@@ -284795,6 +284806,20 @@ async function addCommitStatus(state, sha, repo, owner = 'prefapp', target_url =
284795
284806
  context,
284796
284807
  });
284797
284808
  }
284809
+ async function getRepoIssuesLabels(owner, repo) {
284810
+ github_src_logger.info(`Getting issues labels for ${owner}/${repo}`);
284811
+ const octokit = await getOctokitForOrg(owner);
284812
+ const res = await octokit.issues.listLabelsForRepo({
284813
+ owner: owner,
284814
+ repo: repo,
284815
+ });
284816
+ const metadataLabels = res.data.map((label) => ({
284817
+ name: label.name,
284818
+ description: label.description || null,
284819
+ color: label.color,
284820
+ }));
284821
+ return metadataLabels;
284822
+ }
284798
284823
  /* harmony default export */ const repository = ({
284799
284824
  listReleases,
284800
284825
  getReleaseByTag,
@@ -284803,6 +284828,7 @@ async function addCommitStatus(state, sha, repo, owner = 'prefapp', target_url =
284803
284828
  uploadFile,
284804
284829
  deleteFile,
284805
284830
  getRepoInfo,
284831
+ repoExists,
284806
284832
  getPages,
284807
284833
  getBranchProtection,
284808
284834
  getTeams,
@@ -284810,6 +284836,7 @@ async function addCommitStatus(state, sha, repo, owner = 'prefapp', target_url =
284810
284836
  getOIDCRepo,
284811
284837
  addStatusCheck,
284812
284838
  addCommitStatus,
284839
+ getRepoIssuesLabels,
284813
284840
  });
284814
284841
 
284815
284842
  ;// CONCATENATED MODULE: ../github/src/team.ts
@@ -287910,6 +287937,44 @@ MetadataInitializer.applicableKinds = [
287910
287937
  ];
287911
287938
 
287912
287939
 
287940
+ ;// CONCATENATED MODULE: ../cdk8s_renderer/src/initializers/component_labels.ts
287941
+
287942
+ class ComponentLabelsInitializer extends InitializerPatches {
287943
+ constructor() {
287944
+ super(...arguments);
287945
+ this.applicableProviders = ['github'];
287946
+ }
287947
+ async __validate() {
287948
+ return true;
287949
+ }
287950
+ async __patches(claim, _previousCR) {
287951
+ return [
287952
+ {
287953
+ validate(cr) {
287954
+ if (cr.spec.repo.labels) {
287955
+ const visited = {};
287956
+ for (const label of cr.spec.repo.labels) {
287957
+ if (visited[label.name]) {
287958
+ throw `There is already a label called ${label.name} in the ComponentClaim ${cr.metadata.name}. Labels must be unique`;
287959
+ }
287960
+ visited[label.name] = true;
287961
+ }
287962
+ }
287963
+ return true;
287964
+ },
287965
+ apply(cr) {
287966
+ return cr;
287967
+ },
287968
+ identify() {
287969
+ return 'initializers/ComponentLabels';
287970
+ },
287971
+ },
287972
+ ];
287973
+ }
287974
+ }
287975
+ ComponentLabelsInitializer.applicableKinds = ['ComponentClaim'];
287976
+
287977
+
287913
287978
  ;// CONCATENATED MODULE: ../cdk8s_renderer/src/initializers/index.ts
287914
287979
 
287915
287980
 
@@ -287921,6 +287986,7 @@ MetadataInitializer.applicableKinds = [
287921
287986
 
287922
287987
 
287923
287988
 
287989
+
287924
287990
  const INITIALIZERS = [
287925
287991
  UUIDInitializer,
287926
287992
  InitializerClaimRef,
@@ -287928,6 +287994,7 @@ const INITIALIZERS = [
287928
287994
  PolicyInitializer,
287929
287995
  SyncerInitializer,
287930
287996
  MetadataInitializer,
287997
+ ComponentLabelsInitializer,
287931
287998
  ];
287932
287999
  const INITIALIZERS_BY_FILE_NAME = {
287933
288000
  [TechnologyInitializer.FILE_NAME()]: TechnologyInitializer,
@@ -289337,6 +289404,12 @@ const external_node_child_process_namespaceObject = __WEBPACK_EXTERNAL_createReq
289337
289404
  pattern: '^[a-z0-9][a-z0-9-]*$',
289338
289405
  },
289339
289406
  },
289407
+ labels: {
289408
+ type: 'array',
289409
+ items: {
289410
+ $ref: 'firestartr.dev://github/GithubComponentClaimLabel',
289411
+ },
289412
+ },
289340
289413
  overrides: {
289341
289414
  type: 'object',
289342
289415
  properties: {},
@@ -289545,6 +289618,40 @@ const external_node_child_process_namespaceObject = __WEBPACK_EXTERNAL_createReq
289545
289618
  },
289546
289619
  });
289547
289620
 
289621
+ ;// CONCATENATED MODULE: ../cdk8s_renderer/src/claims/github/component.labels.schema.ts
289622
+
289623
+ /* harmony default export */ const component_labels_schema = ({
289624
+ $schema: SCHEMA,
289625
+ $id: 'GithubComponentClaimLabels',
289626
+ definitions: {
289627
+ GithubComponentClaimLabel: {
289628
+ $id: 'firestartr.dev://github/GithubComponentClaimLabel',
289629
+ type: 'object',
289630
+ required: ['name', 'color'],
289631
+ properties: {
289632
+ name: {
289633
+ type: 'string',
289634
+ minLength: 1,
289635
+ maxLength: 50,
289636
+ pattern: '^(?!\\s+$).+$',
289637
+ description: 'Label name (1-50 chars, not only whitespaces)',
289638
+ },
289639
+ color: {
289640
+ type: 'string',
289641
+ pattern: '^[0-9a-fA-F]{6}$',
289642
+ description: 'Color in hexadecimal without # (6 chars)',
289643
+ },
289644
+ description: {
289645
+ type: 'string',
289646
+ maxLength: 100,
289647
+ description: 'Optional label description (máx 100 chars)',
289648
+ },
289649
+ },
289650
+ additionalProperties: false,
289651
+ },
289652
+ },
289653
+ });
289654
+
289548
289655
  ;// CONCATENATED MODULE: ../cdk8s_renderer/src/claims/github/index.ts
289549
289656
 
289550
289657
 
@@ -289552,6 +289659,7 @@ const external_node_child_process_namespaceObject = __WEBPACK_EXTERNAL_createReq
289552
289659
 
289553
289660
 
289554
289661
 
289662
+
289555
289663
  const GithubSchemas = [
289556
289664
  github_group_schema,
289557
289665
  github_user_schema,
@@ -289559,6 +289667,7 @@ const GithubSchemas = [
289559
289667
  github_component_schema,
289560
289668
  github_orgwebhook_schema,
289561
289669
  component_secrets_vars_schema,
289670
+ component_labels_schema,
289562
289671
  ];
289563
289672
 
289564
289673
  ;// CONCATENATED MODULE: ../cdk8s_renderer/src/claims/tfworkspaces/terraform.schema.ts
@@ -293573,6 +293682,7 @@ function toJson_FirestartrGithubRepositorySpecRepo(obj) {
293573
293682
  'hasWiki': obj.hasWiki,
293574
293683
  'pages': obj.pages,
293575
293684
  'topics': obj.topics?.map(y => y),
293685
+ 'labels': obj.labels?.map(y => toJson_FirestartrGithubRepositorySpecRepoLabels(y)),
293576
293686
  'visibility': obj.visibility,
293577
293687
  'defaultBranch': obj.defaultBranch,
293578
293688
  'additionalBranches': obj.additionalBranches?.map(y => toJson_FirestartrGithubRepositorySpecRepoAdditionalBranches(y)),
@@ -293719,6 +293829,22 @@ function toJson_FirestartrGithubRepositorySpecFirestartrTechnology(obj) {
293719
293829
  // filter undefined values
293720
293830
  return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {});
293721
293831
  }
293832
+ /**
293833
+ * Converts an object of type 'FirestartrGithubRepositorySpecRepoLabels' to JSON representation.
293834
+ */
293835
+ /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */
293836
+ function toJson_FirestartrGithubRepositorySpecRepoLabels(obj) {
293837
+ if (obj === undefined) {
293838
+ return undefined;
293839
+ }
293840
+ const result = {
293841
+ 'name': obj.name,
293842
+ 'color': obj.color,
293843
+ 'description': obj.description,
293844
+ };
293845
+ // filter undefined values
293846
+ return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {});
293847
+ }
293722
293848
  /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */
293723
293849
  /**
293724
293850
  * @schema FirestartrGithubRepositorySpecRepoVisibility
@@ -295714,6 +295840,7 @@ class GithubRepositoryChart extends BaseGithubChart {
295714
295840
  defaultBranch: claim.providers.github?.branchStrategy?.defaultBranch,
295715
295841
  codeowners: createCodeOwnersData(claim),
295716
295842
  additionalBranches: claim.providers.github.additionalBranches || [],
295843
+ labels: claim.providers.github.labels || [],
295717
295844
  topics: claim.providers.github.topics || [],
295718
295845
  },
295719
295846
  actions,
@@ -299074,6 +299201,9 @@ function itemPath(kind, item) {
299074
299201
  }
299075
299202
 
299076
299203
  ;// CONCATENATED MODULE: ../operator/src/definitions.ts
299204
+ // ----------------------------------------------------
299205
+ // Kinds and plural-kinds definitions
299206
+ // ----------------------------------------------------
299077
299207
  const kindPluralMap = {
299078
299208
  githubgroups: 'FirestartrGithubGroup',
299079
299209
  githubmemberships: 'FirestartrGithubMembership',
@@ -299098,6 +299228,25 @@ function definitions_getPluralFromKind(kind) {
299098
299228
  const plural = Object.keys(kindPluralMap).find((key) => kindPluralMap[key] === kind);
299099
299229
  return plural;
299100
299230
  }
299231
+ var OperationType;
299232
+ (function (OperationType) {
299233
+ OperationType["RENAMED"] = "RENAMED";
299234
+ OperationType["UPDATED"] = "UPDATED";
299235
+ OperationType["CREATED"] = "CREATED";
299236
+ OperationType["SYNC"] = "SYNC";
299237
+ OperationType["MARKED_TO_DELETION"] = "MARKED_TO_DELETION";
299238
+ OperationType["NOTHING"] = "NOTHING";
299239
+ OperationType["RETRY"] = "RETRY";
299240
+ })(OperationType || (OperationType = {}));
299241
+ var WorkStatus;
299242
+ (function (WorkStatus) {
299243
+ WorkStatus["PENDING"] = "PENDING";
299244
+ WorkStatus["PROCESSING"] = "PROCESSING";
299245
+ WorkStatus["FINISHED"] = "FINISHED";
299246
+ })(WorkStatus || (WorkStatus = {}));
299247
+ // ----------------------------------------------------
299248
+ // Timeouts for operations, expressed in seconds
299249
+ // ----------------------------------------------------
299101
299250
  const DAY_SECONDS = 24 * 60 * 60;
299102
299251
  const TIMEOUTS = {
299103
299252
  // expressed in seconds
@@ -300507,22 +300656,8 @@ function resolver_walkObject(item) {
300507
300656
 
300508
300657
  ;// CONCATENATED MODULE: ../operator/src/informer.ts
300509
300658
 
300510
- var OperationType;
300511
- (function (OperationType) {
300512
- OperationType["RENAMED"] = "RENAMED";
300513
- OperationType["UPDATED"] = "UPDATED";
300514
- OperationType["CREATED"] = "CREATED";
300515
- OperationType["SYNC"] = "SYNC";
300516
- OperationType["MARKED_TO_DELETION"] = "MARKED_TO_DELETION";
300517
- OperationType["NOTHING"] = "NOTHING";
300518
- OperationType["RETRY"] = "RETRY";
300519
- })(OperationType || (OperationType = {}));
300520
- var WorkStatus;
300521
- (function (WorkStatus) {
300522
- WorkStatus["PENDING"] = "PENDING";
300523
- WorkStatus["PROCESSING"] = "PROCESSING";
300524
- WorkStatus["FINISHED"] = "FINISHED";
300525
- })(WorkStatus || (WorkStatus = {}));
300659
+
300660
+
300526
300661
 
300527
300662
 
300528
300663
 
@@ -300712,6 +300847,7 @@ function enqueue(pluralKind, workItem, queue, compute, syncCtl, retryCtl) {
300712
300847
  return false;
300713
300848
  }
300714
300849
  },
300850
+ getSlotInfo: () => null,
300715
300851
  };
300716
300852
  workItem.process = async function* (item, operation, handler) {
300717
300853
  const needsUpdateSyncConditions = operation === OperationType.RENAMED ||
@@ -301313,7 +301449,9 @@ async function writeDownQueueStatus(queue) {
301313
301449
  for (const workItem of queue) {
301314
301450
  const item = workItem.item;
301315
301451
  const blockedText = workItem.isBlocked ? '[BLOCKED]' : '';
301316
- output += `${item.kind}/${item.metadata.name} - ${workItem.workStatus} - ${workItem.operation} ${blockedText} - (upsert ${formatElapsedTimeWithDate(workItem.upsertTime)})\n`;
301452
+ const slotInfo = workItem.slotId !== undefined ? ` (slot:${workItem.slotId})` : '';
301453
+ const upsertTimeText = formatElapsedTimeWithDate(workItem.upsertTime);
301454
+ output += `${item.kind}/${item.metadata.name} - ${workItem.workStatus} ${slotInfo} - ${workItem.operation} ${blockedText} - (upsert ${upsertTimeText})\n`;
301317
301455
  }
301318
301456
  return new Promise((ok, ko) => {
301319
301457
  external_fs_.writeFile('/tmp/queue', output, (err) => {
@@ -301368,6 +301506,24 @@ function formatElapsedTimeWithDate(upsertTime) {
301368
301506
  return `${parts.slice(0, 2).join(', ')} ago`;
301369
301507
  }
301370
301508
 
301509
+ ;// CONCATENATED MODULE: ../operator/src/signals.ts
301510
+
301511
+ function initSignalsHandler(Mapper) {
301512
+ for (const [signal, callback] of Mapper.entries()) {
301513
+ operator_src_logger.info(`Setting up handler for signal ${signal}... on process with PID ${process.pid}`);
301514
+ process.on(signal, async () => {
301515
+ operator_src_logger.info(`Received signal ${signal}, executing callback...`);
301516
+ try {
301517
+ await callback();
301518
+ operator_src_logger.info(`Callback for signal ${signal} executed successfully.`);
301519
+ }
301520
+ catch (err) {
301521
+ operator_src_logger.error(`Error executing callback for signal ${signal}:`, err);
301522
+ }
301523
+ });
301524
+ }
301525
+ }
301526
+
301371
301527
  ;// CONCATENATED MODULE: ../operator/src/processItemDLH.ts
301372
301528
 
301373
301529
 
@@ -301394,193 +301550,679 @@ async function deadLetterHandler(workItem) {
301394
301550
  }
301395
301551
  }
301396
301552
 
301397
- ;// CONCATENATED MODULE: ../operator/src/signals.ts
301398
-
301399
- const DEFAULT_TERMINATION_GRACE_PERIOD = 2 * 60; // two minutes
301400
- /**
301401
- * Initializes process signal handlers used by the operator.
301402
- *
301403
- * Currently this sets up handling for `SIGTERM`, using the callback
301404
- * associated with the `"SIGTERM"` key from the provided {@link CallbacksMap}
301405
- * to start the shutdown sequence. The returned {@link CallbacksMap} contains
301406
- * handler-specific callbacks (such as `"FINISH_OK"`) that callers can invoke
301407
- * to indicate that it is safe for the process to exit.
301408
- *
301409
- * @param callbacks A map of signal names to shutdown callbacks. The `"SIGTERM"`
301410
- * entry (if present) is invoked when a `SIGTERM` signal is received to
301411
- * perform any necessary cleanup before process termination.
301412
- * @returns A {@link CallbacksMap} with control callbacks that allow the caller
301413
- * to signal when shutdown has completed and the process may safely exit.
301414
- */
301415
- function initSignalsHandler(callbacks) {
301416
- operator_src_logger.info('Starting signals handler');
301417
- const handlerCallbacks = new Map();
301418
- initSigtermHandler(callbacks.get('SIGTERM'), handlerCallbacks);
301419
- return handlerCallbacks;
301420
- }
301421
- function initSigtermHandler(callback, handlerCallbacks) {
301422
- const raw_timeout_to_exit = Number(process.env['FIRESTARTR_TERMINATION_GRACE_PERIOD'] ||
301423
- DEFAULT_TERMINATION_GRACE_PERIOD);
301424
- const timeout_to_exit = raw_timeout_to_exit - Math.round(Math.min(5, raw_timeout_to_exit * 0.1));
301425
- // The switch to control if it is safe to exit
301426
- let finish = false;
301427
- handlerCallbacks.set('FINISH_OK', () => {
301428
- finish = true;
301429
- });
301430
- process.on('SIGTERM', async () => {
301431
- operator_src_logger.info('The controller received a SIGTERM signal');
301432
- // we signal to the callback the sigterm
301433
- callback();
301434
- const timeoutCb = setTimeout(() => {
301435
- operator_src_logger.error('The controller could not shutdown properly. Timeout');
301436
- process.exit(1);
301437
- }, timeout_to_exit * 1000);
301438
- // we wait
301439
- while (!finish) {
301440
- await new Promise((resolve) => setTimeout(resolve, 1000));
301441
- }
301442
- // all is clear!
301443
- clearTimeout(timeoutCb);
301444
- operator_src_logger.info('The controller has properly shutdown. Exiting');
301445
- process.exit(0);
301446
- });
301447
- }
301448
-
301449
- ;// CONCATENATED MODULE: ../operator/src/processItem.ts
301450
-
301451
-
301452
-
301553
+ // EXTERNAL MODULE: ../operator/node_modules/@opentelemetry/exporter-prometheus/build/src/index.js
301554
+ var build_src = __nccwpck_require__(35116);
301555
+ // EXTERNAL MODULE: ../operator/node_modules/@opentelemetry/sdk-metrics/build/src/index.js
301556
+ var sdk_metrics_build_src = __nccwpck_require__(30481);
301557
+ ;// CONCATENATED MODULE: ../operator/src/metrics/CRStates.ts
301453
301558
 
301454
301559
 
301455
301560
 
301456
301561
 
301457
- const queue = [];
301458
- const WEIGHTS = {
301459
- RENAMED: 15,
301460
- UPDATED: 10,
301461
- CREATED: 9,
301462
- RETRY: 8,
301463
- MARKED_TO_DELETION: 6,
301464
- SYNC: 1,
301465
- NOTHING: 0,
301466
- };
301467
- function getQueueMetrics() {
301468
- let renamed = 0;
301469
- let updated = 0;
301470
- let created = 0;
301471
- let sync = 0;
301472
- let marked_to_deletion = 0;
301473
- let nothing = 0;
301474
- let retry = 0;
301475
- let unknown = 0;
301476
- queue.forEach((workItem) => {
301477
- switch (workItem.operation) {
301478
- case 'RENAMED':
301479
- renamed++;
301480
- break;
301481
- case 'UPDATED':
301482
- updated++;
301483
- break;
301484
- case 'CREATED':
301485
- created++;
301486
- break;
301487
- case 'SYNC':
301488
- sync++;
301489
- break;
301490
- case 'MARKED_TO_DELETION':
301491
- marked_to_deletion++;
301492
- break;
301493
- case 'NOTHING':
301494
- nothing++;
301495
- break;
301496
- case 'RETRY':
301497
- retry++;
301498
- break;
301499
- default:
301500
- unknown++;
301501
- break;
301502
- }
301503
- });
301504
- return {
301505
- nItems: queue.length,
301506
- nItemsFinished: queue.filter((workItem) => workItem.workStatus === WorkStatus.FINISHED).length,
301507
- nItemsPending: queue.filter((workItem) => workItem.workStatus === WorkStatus.PENDING).length,
301508
- nItemsProcessing: queue.filter((workItem) => workItem.workStatus === WorkStatus.PROCESSING).length,
301509
- nItemsInDeadLetterHandling: queue.filter((workItem) => workItem.isDeadLetter === true).length,
301510
- nItemsTypes: {
301511
- nothing,
301512
- retry,
301513
- renamed,
301514
- updated,
301515
- sync,
301516
- created,
301517
- marked_to_deletion,
301518
- unknown,
301519
- },
301520
- };
301521
- }
301522
- // we need to assign weight to the deletion operation
301523
- // to avoid blockades
301524
- // https://github.com/prefapp/gitops-k8s/issues/1864
301525
- // ghrepo feat | grss -> ghrepo -> ghgroup -> membership
301526
- const DELETION_WEIGHTS = {
301527
- FirestartrGithubRepositoryFeature: 5,
301528
- FirestartrGithubRepositorySecretsSection: 4,
301529
- FirestartrGithubRepository: 3,
301530
- FirestartrGithubGroup: 2,
301531
- FirestartrGithubMembership: 1,
301532
- FirestartrTerraformWorkspace: 1,
301533
- FirestartrGithubOrgWebhook: 1,
301534
- };
301535
- // Do the kinds need different weights for the operations?
301536
- // We need to discuss this with the team in this issue: https://github.com/prefapp/gitops-k8s/issues/524
301537
- // const KIND_WEIGHTS: any = {
301538
- // "FirestartrTerraformWorkspace": 1,
301539
- // "FirestartrGithubGroup": 1,
301540
- // "FirestartrGithubMembership": 1,
301541
- // "FirestartrGithubRepository": 1,
301542
- // "FirestartrGithubRepositoryFeature": 1,
301543
- // "FirestartrTerraformWorkspacePlan": 1,
301544
- // }
301545
- let INIT = false;
301546
- /**
301547
- * Pushes a WorkItem to the queue
301548
- * @param {WorkItem} workItem - WorkItem to process
301549
- */
301550
- async function processItem(workItem) {
301551
- operator_src_logger.info(`The processor received a new work item for '${workItem.operation}' operation on '${workItem.item.kind}/${workItem.item.metadata.name}' in namespace '${workItem.item.metadata.namespace}'. Current work status is '${workItem.workStatus}'.`);
301552
- queue.push(workItem);
301553
- if (!INIT) {
301554
- processItem_loop().catch((err) => {
301555
- console.error(err);
301562
+ const INTERVAL_IN_SEGS = 60;
301563
+ class CRStateMetrics {
301564
+ constructor(kind, namespace, meter) {
301565
+ this.kind = kind;
301566
+ this.provisionedGauge = meter.createGauge('firestartr_provisioned_total', {
301567
+ description: 'Total number of CRs in PROVISIONED state',
301556
301568
  });
301557
- INIT = true;
301558
- }
301559
- }
301560
- /**
301561
- * Wait until there is a WorkItem to process and then process it
301562
- * @returns {void}
301563
- */
301564
- async function processItem_loop() {
301565
- const nextWorkItem = () => sortQueue(queue)
301566
- .filter((w) => !w.isBlocked)
301567
- .find((w) => w.workStatus === WorkStatus.PENDING);
301568
- loopWorkItemDebug(queue);
301569
- let podIsTerminating = false;
301570
- const handlerCallbacks = initSignalsHandler(new Map([['SIGTERM', () => (podIsTerminating = true)]]));
301569
+ this.provisioningGauge = meter.createGauge('firestartr_provisioning_total', {
301570
+ description: 'Total number of CRs in PROVISIONING state',
301571
+ });
301572
+ this.outOfSyncGauge = meter.createGauge('firestartr_out_of_sync_total', {
301573
+ description: 'Total number of CRs in OUT_OF_SYNC state',
301574
+ });
301575
+ this.errorGauge = meter.createGauge('firestartr_error_total', {
301576
+ description: 'Total number of CRs in ERROR state',
301577
+ });
301578
+ this.planningGauge = meter.createGauge('firestartr_planning_total', {
301579
+ description: 'Total number of CRs in PLANNING state',
301580
+ });
301581
+ this.deletedGauge = meter.createGauge('firestartr_deleted_total', {
301582
+ description: 'Total number of CRs in DELETED state',
301583
+ });
301584
+ this.errorOnSyncGauge = meter.createGauge('firestartr_error_on_sync_total', {
301585
+ description: 'Total number of CRs with failed SYNCs',
301586
+ });
301587
+ this.namespace = namespace;
301588
+ }
301589
+ async start() {
301590
+ await this.__prepareConnection();
301591
+ this.onUpdate = false;
301592
+ this.updateInterval = setInterval(async () => {
301593
+ await this.update();
301594
+ }, 1000 * INTERVAL_IN_SEGS);
301595
+ await this.update();
301596
+ }
301597
+ stop() {
301598
+ if (this.updateInterval) {
301599
+ clearInterval(this.updateInterval);
301600
+ this.updateInterval = null;
301601
+ }
301602
+ }
301603
+ async update() {
301604
+ if (this.onUpdate)
301605
+ return;
301606
+ this.onUpdate = true;
301607
+ try {
301608
+ const items = await this.fListCRs();
301609
+ let provisionedCount = 0;
301610
+ let provisioningCount = 0;
301611
+ let outOfSyncCount = 0;
301612
+ let errorCount = 0;
301613
+ let planningCount = 0;
301614
+ let deletedCount = 0;
301615
+ let errorOnSyncCount = 0;
301616
+ for (const item of items) {
301617
+ const status = item.status?.conditions.find((condition) => condition.type !== 'SYNCHRONIZED' && condition.status === 'True');
301618
+ if (!status)
301619
+ continue;
301620
+ const syncCondition = item.status.conditions.find((condition) => {
301621
+ return condition.type === 'SYNCHRONIZED';
301622
+ });
301623
+ if (syncCondition &&
301624
+ syncCondition.message === SYNC_DEFAULT_ERROR_MESSAGE) {
301625
+ errorOnSyncCount++;
301626
+ }
301627
+ switch (status.type) {
301628
+ case 'PROVISIONED':
301629
+ provisionedCount++;
301630
+ break;
301631
+ case 'PROVISIONING':
301632
+ provisioningCount++;
301633
+ break;
301634
+ case 'OUT_OF_SYNC':
301635
+ outOfSyncCount++;
301636
+ break;
301637
+ case 'PLANNING':
301638
+ planningCount++;
301639
+ break;
301640
+ case 'DELETED':
301641
+ deletedCount++;
301642
+ break;
301643
+ case 'ERROR':
301644
+ errorCount++;
301645
+ break;
301646
+ }
301647
+ }
301648
+ this.provisionedGauge.record(provisionedCount, {
301649
+ namespace: this.namespace,
301650
+ kind: this.kind,
301651
+ });
301652
+ this.provisioningGauge.record(provisioningCount, {
301653
+ namespace: this.namespace,
301654
+ kind: this.kind,
301655
+ });
301656
+ this.planningGauge.record(planningCount, {
301657
+ namespace: this.namespace,
301658
+ kind: this.kind,
301659
+ });
301660
+ this.deletedGauge.record(deletedCount, {
301661
+ namespace: this.namespace,
301662
+ kind: this.kind,
301663
+ });
301664
+ this.outOfSyncGauge.record(outOfSyncCount, {
301665
+ namespace: this.namespace,
301666
+ kind: this.kind,
301667
+ });
301668
+ this.errorGauge.record(errorCount, {
301669
+ namespace: this.namespace,
301670
+ kind: this.kind,
301671
+ });
301672
+ this.errorOnSyncGauge.record(errorOnSyncCount, {
301673
+ namespace: this.namespace,
301674
+ kind: this.kind,
301675
+ });
301676
+ }
301677
+ catch (err) {
301678
+ console.log(`CRStateMetrics: update ${err}`);
301679
+ this.onUpdate = false;
301680
+ operator_src_logger.error(`On update of CR metrics: ${err}`);
301681
+ await this.__prepareConnection();
301682
+ }
301683
+ this.onUpdate = false;
301684
+ }
301685
+ async __prepareConnection() {
301686
+ const { kc, opts } = await ctl_getConnection();
301687
+ const k8sApi = kc.makeApiClient(client_node_dist.CustomObjectsApi);
301688
+ this.fListCRs = async () => {
301689
+ try {
301690
+ const response = await k8sApi.listNamespacedCustomObject('firestartr.dev', 'v1', this.namespace, this.kind);
301691
+ const body = response.body;
301692
+ return body.items;
301693
+ }
301694
+ catch (err) {
301695
+ throw new Error(`On listing CRs ${this.kind}: ${err}`);
301696
+ }
301697
+ };
301698
+ }
301699
+ }
301700
+
301701
+ ;// CONCATENATED MODULE: ../operator/src/metrics/processItem.slot.metrics.ts
301702
+ class ProcessItemSlotMetrics {
301703
+ constructor(id, meter) {
301704
+ this._runningSeconds = 0;
301705
+ this._running = false;
301706
+ this._id = id;
301707
+ this._startTime = Date.now();
301708
+ this._lastStateChangeTime = Date.now();
301709
+ this._runningSeconds = 0;
301710
+ const commonAttributes = { slot_id: String(id) };
301711
+ // Counters (use .add())
301712
+ this._workItemsTotal = meter.createCounter('firestartr_slot.work_items_total', {
301713
+ description: 'Total number of work items processed by this slot',
301714
+ unit: '1',
301715
+ });
301716
+ this._runningTimeSecondsTotal = meter.createCounter('firestartr_slot.running_time_seconds_total', {
301717
+ description: 'Total time this slot has spent running work items',
301718
+ unit: 's',
301719
+ });
301720
+ // Observable Gauges (use .addCallback() + .observe())
301721
+ this._uptimeSeconds = meter.createObservableGauge('firestartr_slot.uptime_seconds', {
301722
+ description: 'Total runtime of this slot since creation (running + idle)',
301723
+ unit: 's',
301724
+ });
301725
+ this._uptimeSeconds.addCallback((observableResult) => {
301726
+ const uptime = (Date.now() - this._startTime) / 1000;
301727
+ observableResult.observe(uptime, commonAttributes);
301728
+ });
301729
+ this._idleTimeSecondsTotal = meter.createObservableGauge('firestartr_slot.idle_time_seconds_total', {
301730
+ description: 'Total time this slot has spent NOT processing items (idle)',
301731
+ unit: 's',
301732
+ });
301733
+ this._idleTimeSecondsTotal.addCallback((observableResult) => {
301734
+ const uptime = (Date.now() - this._startTime) / 1000;
301735
+ const inProgressRunning = this._running
301736
+ ? (Date.now() - this._lastStateChangeTime) / 1000
301737
+ : 0;
301738
+ const idleTime = Math.max(0, uptime - this._runningSeconds - inProgressRunning);
301739
+ observableResult.observe(idleTime, commonAttributes);
301740
+ });
301741
+ }
301742
+ runItemStarted() {
301743
+ this._lastStateChangeTime = Date.now();
301744
+ this._running = true;
301745
+ }
301746
+ runItemFinished() {
301747
+ const now = Date.now();
301748
+ const runningDuration = (now - this._lastStateChangeTime) / 1000;
301749
+ this._runningSeconds += runningDuration;
301750
+ this._runningTimeSecondsTotal.add(runningDuration, {
301751
+ slot_id: String(this._id),
301752
+ });
301753
+ this._workItemsTotal.add(1, { slot_id: String(this._id) });
301754
+ this._lastStateChangeTime = now;
301755
+ this._running = false;
301756
+ }
301757
+ }
301758
+
301759
+ ;// CONCATENATED MODULE: ../operator/src/metrics/processItem.global.metrics.ts
301760
+ // this is a singleton
301761
+ // to track global metrics across all slots, such as average processing time per item,
301762
+ // fastest and slowest processing times, etc.
301763
+ class ProcessorGlobalMetrics {
301764
+ constructor(meter) {
301765
+ this._processedItems = 0;
301766
+ this._totalRunningSeconds = 0;
301767
+ this._minTime = Infinity;
301768
+ this._maxTime = 0;
301769
+ // Global average processing time (no slot_id label)
301770
+ meter
301771
+ .createObservableGauge('firestartr.avg_processing_time_seconds', {
301772
+ description: 'Average processing time per item across ALL slots',
301773
+ unit: 's',
301774
+ })
301775
+ .addCallback((observableResult) => {
301776
+ const avg = this._processedItems > 0
301777
+ ? this._totalRunningSeconds / this._processedItems
301778
+ : 0;
301779
+ observableResult.observe(avg);
301780
+ });
301781
+ // Global fastest
301782
+ meter
301783
+ .createObservableGauge('firestartr.fastest_processing_time_seconds', {
301784
+ description: 'Fastest single item processing time across ALL slots',
301785
+ unit: 's',
301786
+ })
301787
+ .addCallback((observableResult) => {
301788
+ observableResult.observe(this._processedItems > 0 ? this._minTime : 0);
301789
+ });
301790
+ // Global slowest
301791
+ meter
301792
+ .createObservableGauge('firestartr.slowest_processing_time_seconds', {
301793
+ description: 'Slowest single item processing time across ALL slots',
301794
+ unit: 's',
301795
+ })
301796
+ .addCallback((observableResult) => {
301797
+ observableResult.observe(this._processedItems > 0 ? this._maxTime : 0);
301798
+ });
301799
+ }
301800
+ static init(meter) {
301801
+ if (!ProcessorGlobalMetrics._instance) {
301802
+ ProcessorGlobalMetrics._instance = new ProcessorGlobalMetrics(meter);
301803
+ }
301804
+ }
301805
+ static getInstance() {
301806
+ if (!ProcessorGlobalMetrics._instance) {
301807
+ throw new Error('ProcessorGlobalMetrics must be initialized with .init(meter) first');
301808
+ }
301809
+ return ProcessorGlobalMetrics._instance;
301810
+ }
301811
+ recordProcessingDuration(durationSeconds) {
301812
+ if (durationSeconds <= 0)
301813
+ return;
301814
+ this._processedItems++;
301815
+ this._totalRunningSeconds += durationSeconds;
301816
+ this._minTime = Math.min(this._minTime, durationSeconds);
301817
+ this._maxTime = Math.max(this._maxTime, durationSeconds);
301818
+ }
301819
+ }
301820
+ ProcessorGlobalMetrics._instance = null;
301821
+
301822
+
301823
+ ;// CONCATENATED MODULE: ../operator/src/metricsServer.ts
301824
+
301825
+
301826
+
301827
+
301828
+
301829
+
301830
+ let processMetricsSlotProvider = null;
301831
+ /* harmony default export */ async function metricsServer(kindList, namespace) {
301832
+ const portFromEnv = process.env.METRICS_PORT;
301833
+ const options = build_src/* PrometheusExporter.DEFAULT_OPTIONS */.rC.DEFAULT_OPTIONS;
301834
+ options.port = portFromEnv ? parseInt(portFromEnv) : options.port;
301835
+ options.endpoint = process.env.METRICS_ENDPOINT || options.endpoint;
301836
+ const exporter = new build_src/* PrometheusExporter */.rC(options, () => {
301837
+ console.log(`📊 Metrics endpoint: http://localhost:${options.port}${options.endpoint}`);
301838
+ });
301839
+ const attributes = { pid: process.pid };
301840
+ void startMetrics(exporter, attributes, kindList, namespace);
301841
+ }
301842
+ async function startMetrics(exporter, attributes, kindList, namespace) {
301843
+ const meterProvider = new sdk_metrics_build_src/* MeterProvider */.y0({
301844
+ readers: [exporter],
301845
+ });
301846
+ const meter = meterProvider.getMeter('firestartr');
301847
+ void startQueueMetrics(meter, attributes);
301848
+ void startMemoryMetrics(meter, attributes);
301849
+ void startCRStates(meter, kindList, namespace);
301850
+ processMetricsSlotProvider = (slotId) => {
301851
+ return new ProcessItemSlotMetrics(slotId, meter);
301852
+ };
301853
+ ProcessorGlobalMetrics.init(meter);
301854
+ }
301855
+ // whenever a new slot is created, the ProcessItemSlotMetrics instance for that slot can be obtained by calling
301856
+ // getProcessItemSlotMetrics(slotId)
301857
+ function getProcessItemSlotMetrics(slotId) {
301858
+ if (!processMetricsSlotProvider) {
301859
+ throw new Error('ProcessItemSlotMetrics provider not initialized');
301860
+ }
301861
+ return processMetricsSlotProvider(slotId);
301862
+ }
301863
+ async function startQueueMetrics(meter, attributes) {
301864
+ const nWorkItemsCounter = meter.createObservableGauge('firestartr_workitems_total', { description: 'Number of workitems in the queue' });
301865
+ const nWorkItemsPendingCounter = meter.createObservableGauge('firestartr_workitems_pending_total', { description: 'Number of workitems with PENDING status in the queue' });
301866
+ const nWorkItemsProcessingCounter = meter.createObservableGauge('firestartr_workitems_processing_total', { description: 'Number of workitems with PROCESSING status in the queue' });
301867
+ const nWorkItemsFinishedCounter = meter.createObservableGauge('firestartr_workitems_finished_total', { description: 'Number of workitems with FINISHED status in the queue' });
301868
+ const nWorkItemsInDeadLetterHandling = meter.createObservableGauge('firestartr_workitems_dead_letter_handling_total', {
301869
+ description: 'Number of workitems of the queue handled by the Dead Letter Handler',
301870
+ });
301871
+ const queueMetrics = getQueueMetrics();
301872
+ let total = queueMetrics.nItems;
301873
+ let pending = queueMetrics.nItemsPending;
301874
+ let processing = queueMetrics.nItemsProcessing;
301875
+ let finished = queueMetrics.nItemsFinished;
301876
+ let inDeadLetterHandling = queueMetrics.nItemsInDeadLetterHandling;
301877
+ nWorkItemsCounter.addCallback((observer) => observer.observe(total, { ...attributes, ...queueMetrics.nItemsTypes }));
301878
+ nWorkItemsPendingCounter.addCallback((observer) => observer.observe(pending, { ...attributes, ...queueMetrics.nItemsTypes }));
301879
+ nWorkItemsProcessingCounter.addCallback((observer) => observer.observe(processing, {
301880
+ ...attributes,
301881
+ ...queueMetrics.nItemsTypes,
301882
+ }));
301883
+ nWorkItemsFinishedCounter.addCallback((observer) => observer.observe(finished, { ...attributes, ...queueMetrics.nItemsTypes }));
301884
+ nWorkItemsInDeadLetterHandling.addCallback((observer) => observer.observe(inDeadLetterHandling, {
301885
+ ...attributes,
301886
+ ...queueMetrics.nItemsTypes,
301887
+ }));
301888
+ setInterval(() => {
301889
+ const queueMetrics = getQueueMetrics();
301890
+ total = queueMetrics.nItems;
301891
+ pending = queueMetrics.nItemsPending;
301892
+ processing = queueMetrics.nItemsProcessing;
301893
+ finished = queueMetrics.nItemsFinished;
301894
+ inDeadLetterHandling = queueMetrics.nItemsInDeadLetterHandling;
301895
+ }, 1000);
301896
+ }
301897
+ async function startMemoryMetrics(meter, attributes) {
301898
+ const usedMemoryCounter = meter.createObservableGauge('firestartr_used_memory', { description: 'Used memory in bytes' });
301899
+ let heapUsage = process.memoryUsage().heapUsed;
301900
+ usedMemoryCounter.addCallback((observer) => observer.observe(heapUsage, attributes));
301901
+ setInterval(() => {
301902
+ heapUsage = process.memoryUsage().heapUsed;
301903
+ }, 1000);
301904
+ }
301905
+ async function startCRStates(meter, kindList, namespace) {
301906
+ for (const kind of kindList) {
301907
+ const crStateMetrics = new CRStateMetrics(kind, namespace, meter);
301908
+ await crStateMetrics.start();
301909
+ }
301910
+ }
301911
+
301912
+ ;// CONCATENATED MODULE: ../operator/src/processItem.slot.ts
301913
+
301914
+
301915
+
301916
+
301917
+
301918
+
301919
+ function initProcessItemsSlots(nSlots, initMetrics = true) {
301920
+ operator_src_logger.info(`Initializing ${nSlots} processing slots...`);
301921
+ return new ProcessItemSlots(nSlots, initMetrics);
301922
+ }
301923
+ class ProcessItemSlots {
301924
+ constructor(nSlots, initMetrics) {
301925
+ this._guardRails = new GuardRails();
301926
+ // let's init the slots
301927
+ this._nSlots = nSlots;
301928
+ this._slots = new Array(nSlots).fill(0).map((_, i) => {
301929
+ const slot = new ProcessItemSlot(i);
301930
+ if (initMetrics) {
301931
+ try {
301932
+ slot.metrics = getProcessItemSlotMetrics(i);
301933
+ }
301934
+ catch (error) {
301935
+ operator_src_logger.warn(`Process item slot metrics unavailable for slot ${i}; continuing without per-slot metrics.`, error);
301936
+ }
301937
+ }
301938
+ slot.actions = {
301939
+ blockItem: (item) => this._guardRails.block(item),
301940
+ unblockItem: (item) => this._guardRails.unblock(item),
301941
+ };
301942
+ return slot;
301943
+ });
301944
+ }
301945
+ *getIdleSlots() {
301946
+ for (const slot of this._slots) {
301947
+ if (slot.idle)
301948
+ yield slot;
301949
+ }
301950
+ }
301951
+ allSlotsAreIdle() {
301952
+ return this._slots.every((slot) => slot.idle);
301953
+ }
301954
+ isWorkItemBlocked(workItem) {
301955
+ return this._guardRails.isBlocked(workItem.item);
301956
+ }
301957
+ }
301958
+ class GuardRails {
301959
+ constructor() {
301960
+ this._processing = new Map();
301961
+ }
301962
+ block(item) {
301963
+ const itemPath = this.itemToGuard(item);
301964
+ this._processing.set(itemPath, true);
301965
+ }
301966
+ unblock(item) {
301967
+ const itemPath = this.itemToGuard(item);
301968
+ this._processing.delete(itemPath);
301969
+ }
301970
+ isBlocked(item) {
301971
+ const itemPath = this.itemToGuard(item);
301972
+ return this._processing.has(itemPath);
301973
+ }
301974
+ itemToGuard(item) {
301975
+ return `${item.kind}/${item.metadata.namespace}/${item.metadata.name}`;
301976
+ }
301977
+ }
301978
+ class ProcessItemSlot {
301979
+ constructor(id) {
301980
+ this._id = id;
301981
+ this._idle = true;
301982
+ }
301983
+ set actions(actions) {
301984
+ this._actions = actions;
301985
+ }
301986
+ get actions() {
301987
+ return this._actions;
301988
+ }
301989
+ get idle() {
301990
+ return this._idle;
301991
+ }
301992
+ get id() {
301993
+ return this._id;
301994
+ }
301995
+ set metrics(metrics) {
301996
+ this._metrics = metrics;
301997
+ }
301998
+ get metrics() {
301999
+ return this._metrics;
302000
+ }
302001
+ async runWorkItem(workItem) {
302002
+ this._idle = false;
302003
+ const startTime = Date.now(); // ← capture exact start for global metrics
302004
+ try {
302005
+ if (this.actions) {
302006
+ this.actions.blockItem(workItem.item);
302007
+ }
302008
+ if (this.metrics) {
302009
+ this.metrics.runItemStarted();
302010
+ }
302011
+ await this.__run(workItem);
302012
+ }
302013
+ catch (e) {
302014
+ if (e instanceof Error &&
302015
+ e.message.includes('Error on getItemByItemPath')) {
302016
+ operator_src_logger.debug(`Item '${workItem.item.kind}/${workItem.item.metadata.name}' was not found, so its work item is being removed from the processor queue.`);
302017
+ workItem.workStatus = WorkStatus.FINISHED;
302018
+ }
302019
+ else {
302020
+ operator_src_logger.error(`An unmanaged error occurred while the processor was handling the '${workItem.operation}' operation for item '${workItem.item.kind}/${workItem.item.metadata.name}' in namespace '${workItem.item.metadata.namespace}'. Current work status is '${workItem.workStatus}'. The error was: '${e}'.`);
302021
+ await deadLetterHandler(workItem);
302022
+ console.error(e);
302023
+ }
302024
+ }
302025
+ finally {
302026
+ if (this.actions) {
302027
+ this.actions.unblockItem(workItem.item);
302028
+ }
302029
+ // if metrics are enabled, we record the processing duration for this item
302030
+ // and update the global metrics
302031
+ if (this.metrics) {
302032
+ this.metrics.runItemFinished();
302033
+ // REPORT TO GLOBAL SINGLETON for metrics
302034
+ const durationSeconds = (Date.now() - startTime) / 1000;
302035
+ ProcessorGlobalMetrics.getInstance().recordProcessingDuration(durationSeconds);
302036
+ }
302037
+ workItem.isPicked = false;
302038
+ }
302039
+ this._idle = true;
302040
+ }
302041
+ async __run(workItem) {
302042
+ if (!workItem.getItem || !workItem.process || !workItem.operation)
302043
+ return;
302044
+ const item = await workItem.getItem();
302045
+ // we check if the workItem needs blocking
302046
+ // if it does need it we return because we cannot
302047
+ // process this item
302048
+ if ('needsBlocking' in workItem.handler &&
302049
+ workItem.handler.needsBlocking(item, workItem.operation)) {
302050
+ operator_src_logger.debug(`Item ${item.kind}/${item.metadata.namespace} needs blocking`);
302051
+ return;
302052
+ }
302053
+ workItem.workStatus = WorkStatus.PROCESSING;
302054
+ workItem.slotId = this.id;
302055
+ workItem.handler.getSlotInfo = () => ({ slotId: this.id });
302056
+ for await (const condition of workItem.process(item, workItem.operation, workItem.handler)) {
302057
+ if (workItem.handler === undefined)
302058
+ throw new Error('handler is undefined');
302059
+ await updateTransition(workItem.handler.itemPath(), condition.reason, condition.type, condition.status, condition.message, condition.updateStatusOnly || false);
302060
+ }
302061
+ workItem.workStatus = WorkStatus.FINISHED;
302062
+ }
302063
+ }
302064
+
302065
+ ;// CONCATENATED MODULE: ../operator/src/processItem.ts
302066
+
302067
+
302068
+
302069
+
302070
+
302071
+
302072
+ const queue = [];
302073
+ const WEIGHTS = {
302074
+ RENAMED: 15,
302075
+ UPDATED: 10,
302076
+ CREATED: 9,
302077
+ RETRY: 8,
302078
+ MARKED_TO_DELETION: 6,
302079
+ SYNC: 1,
302080
+ NOTHING: 0,
302081
+ };
302082
+ // We get the number of max slots from the environment variable, if it is not set, we default to 1
302083
+ function getMaxSlotsFromEnvironment() {
302084
+ const rawMaxSlots = catalog_common.environment.getFromEnvironment(catalog_common.types.envVars.operatorNumberOfMaxSlots);
302085
+ const parsedMaxSlots = Number.parseInt(rawMaxSlots || '1', 10);
302086
+ if (!Number.isFinite(parsedMaxSlots) || parsedMaxSlots < 1) {
302087
+ return 1;
302088
+ }
302089
+ return parsedMaxSlots;
302090
+ }
302091
+ const MAX_SLOTS = getMaxSlotsFromEnvironment();
302092
+ function getQueueMetrics() {
302093
+ let renamed = 0;
302094
+ let updated = 0;
302095
+ let created = 0;
302096
+ let sync = 0;
302097
+ let marked_to_deletion = 0;
302098
+ let nothing = 0;
302099
+ let retry = 0;
302100
+ let unknown = 0;
302101
+ queue.forEach((workItem) => {
302102
+ switch (workItem.operation) {
302103
+ case 'RENAMED':
302104
+ renamed++;
302105
+ break;
302106
+ case 'UPDATED':
302107
+ updated++;
302108
+ break;
302109
+ case 'CREATED':
302110
+ created++;
302111
+ break;
302112
+ case 'SYNC':
302113
+ sync++;
302114
+ break;
302115
+ case 'MARKED_TO_DELETION':
302116
+ marked_to_deletion++;
302117
+ break;
302118
+ case 'NOTHING':
302119
+ nothing++;
302120
+ break;
302121
+ case 'RETRY':
302122
+ retry++;
302123
+ break;
302124
+ default:
302125
+ unknown++;
302126
+ break;
302127
+ }
302128
+ });
302129
+ return {
302130
+ nItems: queue.length,
302131
+ nItemsFinished: queue.filter((workItem) => workItem.workStatus === WorkStatus.FINISHED).length,
302132
+ nItemsPending: queue.filter((workItem) => workItem.workStatus === WorkStatus.PENDING).length,
302133
+ nItemsProcessing: queue.filter((workItem) => workItem.workStatus === WorkStatus.PROCESSING).length,
302134
+ nItemsInDeadLetterHandling: queue.filter((workItem) => workItem.isDeadLetter === true).length,
302135
+ nItemsTypes: {
302136
+ nothing,
302137
+ retry,
302138
+ renamed,
302139
+ updated,
302140
+ sync,
302141
+ created,
302142
+ marked_to_deletion,
302143
+ unknown,
302144
+ },
302145
+ };
302146
+ }
302147
+ // we need to assign weight to the deletion operation
302148
+ // to avoid blockades
302149
+ // https://github.com/prefapp/gitops-k8s/issues/1864
302150
+ // ghrepo feat | grss -> ghrepo -> ghgroup -> membership
302151
+ const DELETION_WEIGHTS = {
302152
+ FirestartrGithubRepositoryFeature: 5,
302153
+ FirestartrGithubRepositorySecretsSection: 4,
302154
+ FirestartrGithubRepository: 3,
302155
+ FirestartrGithubGroup: 2,
302156
+ FirestartrGithubMembership: 1,
302157
+ FirestartrTerraformWorkspace: 1,
302158
+ FirestartrGithubOrgWebhook: 1,
302159
+ };
302160
+ // Do the kinds need different weights for the operations?
302161
+ // We need to discuss this with the team in this issue: https://github.com/prefapp/gitops-k8s/issues/524
302162
+ // const KIND_WEIGHTS: any = {
302163
+ // "FirestartrTerraformWorkspace": 1,
302164
+ // "FirestartrGithubGroup": 1,
302165
+ // "FirestartrGithubMembership": 1,
302166
+ // "FirestartrGithubRepository": 1,
302167
+ // "FirestartrGithubRepositoryFeature": 1,
302168
+ // "FirestartrTerraformWorkspacePlan": 1,
302169
+ // }
302170
+ let INIT = false;
302171
+ /**
302172
+ * Pushes a WorkItem to the queue
302173
+ * @param {WorkItem} workItem - WorkItem to process
302174
+ */
302175
+ async function processItem(workItem) {
302176
+ operator_src_logger.info(`The processor received a new work item for '${workItem.operation}' operation on '${workItem.item.kind}/${workItem.item.metadata.name}' in namespace '${workItem.item.metadata.namespace}'. Current work status is '${workItem.workStatus}'.`);
302177
+ queue.push(workItem);
302178
+ if (!INIT) {
302179
+ processItem_loop().catch((err) => {
302180
+ console.error(err);
302181
+ });
302182
+ INIT = true;
302183
+ }
302184
+ }
302185
+ /**
302186
+ * Wait until there is a WorkItem to process and then process it
302187
+ * @returns {void}
302188
+ */
302189
+ async function processItem_loop() {
302190
+ loopWorkItemDebug(queue);
302191
+ let podIsTerminating = false;
302192
+ const processItemSlotsManager = initProcessItemsSlots(MAX_SLOTS);
302193
+ const nextWorkItem = () => sortQueue(queue)
302194
+ .filter((w) => !w.isBlocked &&
302195
+ !w.isPicked &&
302196
+ !processItemSlotsManager.isWorkItemBlocked(w))
302197
+ .find((w) => w.workStatus === WorkStatus.PENDING);
302198
+ initSignalsHandler(new Map([['SIGTERM', () => (podIsTerminating = true)]]));
302199
+ operator_src_logger.info(`
302200
+ ------- Processor main loop started with a maximum of '${MAX_SLOTS}' concurrent slots to process items. -------
302201
+ `);
301571
302202
  while (1) {
301572
- if (podIsTerminating) {
302203
+ if (podIsTerminating && processItemSlotsManager.allSlotsAreIdle()) {
301573
302204
  operator_src_logger.info('The processor is going to shutdown (pod is terminating)');
301574
- // we notify the handler that is ok to shutdown
301575
- handlerCallbacks.get('FINISH_OK')();
302205
+ process.exit(0);
301576
302206
  break;
301577
302207
  }
301578
- const w = nextWorkItem();
301579
- if (w) {
301580
- const logMessage = `${new Date().toISOString()} : Processing OPERATION: ${w.operation} ITEM: ${w.item.kind}/${w.item.metadata.name}`;
301581
- catalog_common.io.writeLogFile('process_item', logMessage);
301582
- operator_src_logger.info(`The processor is currently handling a '${w.operation}' operation for item '${w.item.kind}/${w.item.metadata.name}' in namespace '${w.item.metadata.namespace}'. The current work status is '${w.workStatus}'.`);
301583
- await runWorkItem(w);
302208
+ // every time an idle slot is available, we check if there is a work item to process and if there is, we process it
302209
+ // if there are more idle slots than work items, the next iterations of the loop will just do nothing
302210
+ // until there are new work items to process
302211
+ for (const idleSlot of processItemSlotsManager.getIdleSlots()) {
302212
+ if (podIsTerminating) {
302213
+ break;
302214
+ }
302215
+ const w = nextWorkItem();
302216
+ if (w) {
302217
+ // synchronously mark the work item as picked
302218
+ w.isPicked = true;
302219
+ const logMessage = `${new Date().toISOString()} : Processing OPERATION: ${w.operation} ITEM: ${w.item.kind}/${w.item.metadata.name}`;
302220
+ catalog_common.io.writeLogFile('process_item', logMessage);
302221
+ operator_src_logger.info(`The processor (${idleSlot.id}) is currently handling a '${w.operation}' operation for item '${w.item.kind}/${w.item.metadata.name}' in namespace '${w.item.metadata.namespace}'. The current work status is '${w.workStatus}'.`);
302222
+ // we do not wait for the item to be processed to start processing the next one,
302223
+ // we just start processing it in a new slot
302224
+ void idleSlot.runWorkItem(w);
302225
+ }
301584
302226
  }
301585
302227
  await processItem_wait();
301586
302228
  }
@@ -301625,44 +302267,6 @@ function sortQueue(queue) {
301625
302267
  function processItem_wait(t = 2000) {
301626
302268
  return new Promise((ok) => setTimeout(ok, t));
301627
302269
  }
301628
- async function runWorkItem(workItem) {
301629
- operator_src_logger.debug(`The processor is now running the '${workItem.operation}' operation for item '${workItem.item.kind}/${workItem.item.metadata.name}' in namespace '${workItem.item.metadata.namespace}'.`);
301630
- if (!workItem.getItem || !workItem.process || !workItem.operation)
301631
- return;
301632
- try {
301633
- const item = await workItem.getItem();
301634
- // we check if the workItem needs blocking
301635
- // if it does need it we return because we cannot
301636
- // process this item
301637
- if ('needsBlocking' in workItem.handler &&
301638
- workItem.handler.needsBlocking(item, workItem.operation)) {
301639
- operator_src_logger.debug(`Item ${item.kind}/${item.metadata.namespace} needs blocking`);
301640
- return;
301641
- }
301642
- workItem.workStatus = WorkStatus.PROCESSING;
301643
- for await (const condition of workItem.process(item, workItem.operation, workItem.handler)) {
301644
- if (workItem.handler === undefined)
301645
- throw new Error('handler is undefined');
301646
- await updateTransition(workItem.handler.itemPath(), condition.reason, condition.type, condition.status, condition.message, condition.updateStatusOnly || false);
301647
- }
301648
- workItem.workStatus = WorkStatus.FINISHED;
301649
- operator_src_logger.debug(`The processor has '${queue.length}' items remaining in the queue.`);
301650
- }
301651
- catch (e) {
301652
- if (e instanceof Error &&
301653
- e.message.includes('Error on getItemByItemPath')) {
301654
- operator_src_logger.debug(`Item '${workItem.item.kind}/${workItem.item.metadata.name}' was not found, so its work item is being removed from the processor queue.`);
301655
- workItem.workStatus = WorkStatus.FINISHED;
301656
- return;
301657
- }
301658
- else {
301659
- operator_src_logger.error(`An unmanaged error occurred while the processor was handling the '${workItem.operation}' operation for item '${workItem.item.kind}/${workItem.item.metadata.name}' in namespace '${workItem.item.metadata.namespace}'. Current work status is '${workItem.workStatus}'. The error was: '${e}'.`);
301660
- await deadLetterHandler(workItem);
301661
- console.error(e);
301662
- }
301663
- return;
301664
- }
301665
- }
301666
302270
  /**
301667
302271
  * Periodically checks the queue for finished WorkItems and removes them
301668
302272
  * @param {WorkItem[]} queue - Queue of WorkItems
@@ -301869,15 +302473,16 @@ function processHandler(processToHandle, ctl, onTimedOut) {
301869
302473
 
301870
302474
 
301871
302475
 
302476
+
301872
302477
  const TOFU_LOCK_TIMEOUT = '-lock-timeout=60s';
301873
302478
  async function utils_validate(path, secrets) {
301874
302479
  return await tfExec(path, ['validate'], secrets);
301875
302480
  }
301876
- async function init(path, secrets, stream) {
301877
- return await tfExec(path, ['init'], secrets, ['-input=false'], stream);
302481
+ async function init(path, secrets, stream, ctl) {
302482
+ return await tfExec(path, ['init'], secrets, ['-input=false'], stream, ctl);
301878
302483
  }
301879
- async function initFromModule(path, source, secrets, stream) {
301880
- return tfExec(path, ['init', `-from-module=${source}`], secrets, [], stream);
302484
+ async function initFromModule(path, source, secrets, stream, ctl) {
302485
+ return tfExec(path, ['init', `-from-module=${source}`], secrets, [], stream, ctl);
301881
302486
  }
301882
302487
  async function plan(path, secrets, format, args = ['plan'], stream, ctl) {
301883
302488
  terraform_provisioner_src_logger.info(`Running terraform plan with ${format} in path ${path}`);
@@ -301905,10 +302510,27 @@ async function output(path, secrets) {
301905
302510
  return await tfExec(path, ['output', '-json'], secrets, []);
301906
302511
  }
301907
302512
  async function tfExec(path, args, secrets, extraArgs = ['-input=false'], stream, ctl) {
302513
+ const env = {};
302514
+ const tfCacheDir = typeof ctl?.tfCacheDir === 'string' && ctl.tfCacheDir.trim() !== ''
302515
+ ? ctl.tfCacheDir
302516
+ : undefined;
302517
+ if (tfCacheDir) {
302518
+ env['TF_PLUGIN_CACHE_DIR'] = tfCacheDir;
302519
+ try {
302520
+ external_fs_.mkdirSync(tfCacheDir, { recursive: true });
302521
+ }
302522
+ catch (error) {
302523
+ throw new Error(`Failed to create TF plugin cache directory "${tfCacheDir}": ${error?.message ?? error}`);
302524
+ }
302525
+ }
301908
302526
  return new Promise((ok, ko) => {
301909
302527
  const tfProcess = (0,external_child_process_.spawn)('tofu', args.concat(extraArgs), {
301910
302528
  cwd: path,
301911
302529
  stdio: ['inherit', 'pipe', 'pipe'],
302530
+ env: {
302531
+ ...process.env,
302532
+ ...env,
302533
+ },
301912
302534
  });
301913
302535
  let output = '';
301914
302536
  let flagStdoutEnd = false;
@@ -302527,10 +303149,7 @@ class project_tf_TFProjectManager {
302527
303149
  }
302528
303150
  }
302529
303151
  async __init() {
302530
- this.tfOutput += await init(this.projectPath, this.secrets, this.stream);
302531
- }
302532
- async __initFromModule() {
302533
- this.tfOutput += await init(this.projectPath, this.secrets, this.stream);
303152
+ this.tfOutput += await init(this.projectPath, this.secrets, this.stream, this.ctl);
302534
303153
  }
302535
303154
  async validate() {
302536
303155
  await this.__init();
@@ -302758,11 +303377,11 @@ class TFProjectManagerRemote {
302758
303377
  insteadOf = https://github.com`);
302759
303378
  }
302760
303379
  async __init() {
302761
- this.tfOutput += await init(this.projectPath, this.secrets);
303380
+ this.tfOutput += await init(this.projectPath, this.secrets, undefined, this.ctl);
302762
303381
  }
302763
303382
  async __initFromModule() {
302764
303383
  external_fs_.mkdirSync(this.projectPath, { recursive: true });
302765
- this.tfOutput += await initFromModule(this.projectPath, this.ctx.module, this.secrets);
303384
+ this.tfOutput += await initFromModule(this.projectPath, this.ctx.module, this.secrets, undefined, this.ctl);
302766
303385
  }
302767
303386
  async validate() {
302768
303387
  await this.__init();
@@ -303330,9 +303949,10 @@ function extractErrorDetails(error) {
303330
303949
 
303331
303950
 
303332
303951
  const TF_PROJECTS_PATH = '/tmp/tfworkspaces';
303952
+ const TF_CACHES_PATH = '/tmp/tfcaches';
303333
303953
  function processOperation(item, op, handler) {
303334
303954
  try {
303335
- clearLocalTfProjects();
303955
+ clearLocalTfProject(item);
303336
303956
  const policy = getPolicy(item, 'firestartr.dev/policy');
303337
303957
  const syncPolicy = getPolicy(item, 'firestartr.dev/sync-policy');
303338
303958
  if (!policy || policy === 'observe' || policy === 'observe-only') {
@@ -303406,6 +304026,7 @@ async function* doPlanJSONFormat(item, op, handler, setResult = function (_r) {
303406
304026
  await addPlanStatusCheck(item.metadata.annotations['firestartr.dev/last-state-pr'], 'Terraform plan in progress...');
303407
304027
  }
303408
304028
  const tfPlan = await runTerraformProvisioner(context, planType, null, {
304029
+ tfCacheDir: external_path_.join(TF_CACHES_PATH, `slot_${handler.getSlotInfo().slotId}`),
303409
304030
  hardTimeout: handler.recommendedTimeout(),
303410
304031
  processKilled: (killed) => {
303411
304032
  if (killed) {
@@ -303703,6 +304324,7 @@ async function* process_operation_markedToDeletion(item, op, handler) {
303703
304324
  const deps = await handler.resolveReferences();
303704
304325
  const context = buildProvisionerContext(item, deps);
303705
304326
  const destroyOutput = await runTerraformProvisioner(context, 'destroy', null, {
304327
+ tfCacheDir: external_path_.join(TF_CACHES_PATH, `slot_${handler.getSlotInfo().slotId}`),
303706
304328
  hardTimeout: handler.recommendedTimeout(),
303707
304329
  processKilled: (killed) => {
303708
304330
  if (killed) {
@@ -303824,6 +304446,7 @@ async function* doApply(item, op, handler) {
303824
304446
  operator_src_logger.info(`The Terraform processor is applying and assessing dependencies for item '${item.kind}/${item.metadata.name}' with dependencies: '${deps}'.`);
303825
304447
  const context = buildProvisionerContext(item, deps);
303826
304448
  const applyOutput = await runTerraformProvisioner(context, 'apply', checkRunCtl, {
304449
+ tfCacheDir: external_path_.join(TF_CACHES_PATH, `slot_${handler.getSlotInfo().slotId}`),
303827
304450
  hardTimeout: handler.recommendedTimeout(),
303828
304451
  processKilled: (killed) => {
303829
304452
  if (killed) {
@@ -304119,8 +304742,72 @@ function resolveReferences(item, deps) {
304119
304742
  throw new Error(`resolving references: ${e}`);
304120
304743
  }
304121
304744
  }
304122
- function clearLocalTfProjects() {
304123
- external_fs_.rmSync(TF_PROJECTS_PATH, { recursive: true, force: true });
304745
+ function getValidatedTfProjectPath(item) {
304746
+ const tfStateKey = item?.spec?.firestartr?.tfStateKey;
304747
+ if (typeof tfStateKey !== 'string' || tfStateKey.trim() === '') {
304748
+ throw new Error('Invalid terraform state key');
304749
+ }
304750
+ if (external_path_.isAbsolute(tfStateKey)) {
304751
+ throw new Error(`Invalid terraform state key: ${tfStateKey}`);
304752
+ }
304753
+ const basePath = external_path_.resolve(TF_PROJECTS_PATH);
304754
+ const tfProjectPath = external_path_.resolve(basePath, tfStateKey);
304755
+ const relativePath = external_path_.relative(basePath, tfProjectPath);
304756
+ if (relativePath === '' ||
304757
+ relativePath === '.' ||
304758
+ relativePath.startsWith('..') ||
304759
+ external_path_.isAbsolute(relativePath)) {
304760
+ throw new Error(`Invalid terraform state key: ${tfStateKey}`);
304761
+ }
304762
+ return tfProjectPath;
304763
+ }
304764
+ function getValidatedTfProjectsRealPath() {
304765
+ const basePath = external_path_.resolve(TF_PROJECTS_PATH);
304766
+ external_fs_.mkdirSync(basePath, { recursive: true });
304767
+ const realBasePath = external_fs_.realpathSync(basePath);
304768
+ if (!external_fs_.statSync(realBasePath).isDirectory()) {
304769
+ throw new Error(`Invalid terraform projects path: ${TF_PROJECTS_PATH}`);
304770
+ }
304771
+ return realBasePath;
304772
+ }
304773
+ function assertPathWithinBase(candidatePath, realBasePath) {
304774
+ const relativePath = external_path_.relative(realBasePath, candidatePath);
304775
+ if (relativePath === '' ||
304776
+ relativePath === '.' ||
304777
+ relativePath.startsWith('..') ||
304778
+ external_path_.isAbsolute(relativePath)) {
304779
+ throw new Error(`Invalid terraform project path: ${candidatePath}`);
304780
+ }
304781
+ }
304782
+ function assertNoSymlinksInExistingPathComponents(realBasePath, candidatePath) {
304783
+ const relativePath = external_path_.relative(realBasePath, candidatePath);
304784
+ const pathSegments = relativePath
304785
+ .split(external_path_.sep)
304786
+ .filter((segment) => segment !== '');
304787
+ let currentPath = realBasePath;
304788
+ for (const pathSegment of pathSegments) {
304789
+ currentPath = external_path_.join(currentPath, pathSegment);
304790
+ if (!external_fs_.existsSync(currentPath)) {
304791
+ return;
304792
+ }
304793
+ if (external_fs_.lstatSync(currentPath).isSymbolicLink()) {
304794
+ throw new Error(`Invalid terraform project path: symlink component detected at ${currentPath}`);
304795
+ }
304796
+ }
304797
+ }
304798
+ function clearLocalTfProject(item) {
304799
+ const tfProjectPath = getValidatedTfProjectPath(item);
304800
+ const realBasePath = getValidatedTfProjectsRealPath();
304801
+ assertNoSymlinksInExistingPathComponents(realBasePath, tfProjectPath);
304802
+ let deletePath = tfProjectPath;
304803
+ if (external_fs_.existsSync(tfProjectPath)) {
304804
+ deletePath = external_fs_.realpathSync(tfProjectPath);
304805
+ assertPathWithinBase(deletePath, realBasePath);
304806
+ }
304807
+ external_fs_.rmSync(deletePath, {
304808
+ recursive: true,
304809
+ force: true,
304810
+ });
304124
304811
  }
304125
304812
  function getErrorOutputMessage(cr, key, ref) {
304126
304813
  if (cr.spec.source === 'Remote') {
@@ -304269,7 +304956,7 @@ async function acquireLease(namespace, cb, interval = 10000) {
304269
304956
  const processOperationPlan_TF_PROJECTS_PATH = '/tmp/tfworkspaces';
304270
304957
  function processOperationPlan(item, op, handler) {
304271
304958
  try {
304272
- processOperationPlan_clearLocalTfProjects();
304959
+ clearLocalTfProjects();
304273
304960
  const policy = processOperationPlan_getPolicy(item);
304274
304961
  if (policy === 'observe' || policy === 'apply') {
304275
304962
  return processOperationPlan_plan(item, op, handler);
@@ -304695,7 +305382,7 @@ function processOperationPlan_resolveReferences(item, deps) {
304695
305382
  throw { error: `resolving references: ${e}` };
304696
305383
  }
304697
305384
  }
304698
- function processOperationPlan_clearLocalTfProjects() {
305385
+ function clearLocalTfProjects() {
304699
305386
  external_fs_.rmSync(processOperationPlan_TF_PROJECTS_PATH, { recursive: true, force: true });
304700
305387
  }
304701
305388
  function processOperationPlan_getErrorOutputMessage(cr, key, ref) {
@@ -305081,7 +305768,7 @@ const MODULES = {
305081
305768
  },
305082
305769
  FirestartrGithubRepository: {
305083
305770
  module: 'git::https://github.com/prefapp/tfm.git//modules/github-repo',
305084
- ref: 'fix/remove-topics-from-creation-to-avoid-crashes',
305771
+ ref: 'feat/add-labels-support-gh-repo',
305085
305772
  },
305086
305773
  FirestartrGithubRepositoryFeature: {
305087
305774
  module: 'git::https://github.com/prefapp/tfm.git//modules/github-files-set',
@@ -305660,20 +306347,98 @@ function provisionPermissions(fsGithubRepository) {
305660
306347
  });
305661
306348
  }
305662
306349
  }
305663
- else {
305664
- gh_provisioner_src_logger.info(`[gh-provisioner] ${fsGithubRepository.k8sId} provisions a member ${permission.collaborator} with role ${permission.role}`);
305665
- const config = {
305666
- username: permission.collaborator,
305667
- permission: permission.role,
305668
- };
306350
+ else {
306351
+ gh_provisioner_src_logger.info(`[gh-provisioner] ${fsGithubRepository.k8sId} provisions a member ${permission.collaborator} with role ${permission.role}`);
306352
+ const config = {
306353
+ username: permission.collaborator,
306354
+ permission: permission.role,
306355
+ };
306356
+ fsGithubRepository.patchData({
306357
+ op: PatchOperations.add,
306358
+ path: '/config/collaborators/-',
306359
+ value: config,
306360
+ });
306361
+ }
306362
+ }
306363
+ }
306364
+
306365
+ ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/helpers/labels.ts
306366
+ // if we are to provision labels using tofu and the label already
306367
+ // exists, we should not attempt to re-create it. This function checks
306368
+ // if the label exists and if not, creates it. This is needed because
306369
+ // tofu does not have an "upsert" operation for labels, so we need to check
306370
+ // if the label exists before trying to create it, otherwise we will get an error.
306371
+
306372
+
306373
+
306374
+ async function provisionLabels(fsGithubRepository, repoAlreadyExists) {
306375
+ try {
306376
+ const cr = fsGithubRepository.cr;
306377
+ if (cr.spec.repo.labels.length === 0) {
306378
+ return;
306379
+ }
306380
+ if (repoAlreadyExists) {
306381
+ await importLabelsIfNeeded(fsGithubRepository);
306382
+ }
306383
+ const labels = cr.spec.repo.labels;
306384
+ for (const label of labels) {
306385
+ gh_provisioner_src_logger.debug(`[gh-provisioner] ${fsGithubRepository.k8sId} provisioning label ${label.name}`);
305669
306386
  fsGithubRepository.patchData({
305670
306387
  op: PatchOperations.add,
305671
- path: '/config/collaborators/-',
305672
- value: config,
306388
+ path: '/config/labels/-',
306389
+ value: {
306390
+ name: label.name,
306391
+ color: label.color,
306392
+ description: label.description,
306393
+ },
306394
+ });
306395
+ }
306396
+ }
306397
+ catch (err) {
306398
+ gh_provisioner_src_logger.error(`[gh-provisioner] ${fsGithubRepository.k8sId} error on provisionLabels: ${err}`);
306399
+ throw new Error(`Error on provisionLabels: ${err}`);
306400
+ }
306401
+ }
306402
+ async function importLabelsIfNeeded(fsGithubRepository) {
306403
+ const cr = fsGithubRepository.cr;
306404
+ try {
306405
+ const labels = cr.spec.repo.labels;
306406
+ const labelsToImport = [];
306407
+ gh_provisioner_src_logger.info(`[gh-provisioner] ${fsGithubRepository.k8sId} checking if labels need to be imported`);
306408
+ const repoIssuesList = await fsGithubRepository.runWithGithubProvider(async () => {
306409
+ return await github.repo.getRepoIssuesLabels(cr.spec.org, cr.name);
306410
+ });
306411
+ // we need to check wether the label exists or not, if it does not exist we need to create it
306412
+ for (const label of labels) {
306413
+ if (checkIfLabelExists(label.name, repoIssuesList)) {
306414
+ continue;
306415
+ }
306416
+ labelsToImport.push({
306417
+ name: label.name,
306418
+ color: label.color,
306419
+ description: label.description,
306420
+ });
306421
+ }
306422
+ for (const label of labelsToImport) {
306423
+ this.patchImportData({
306424
+ op: PatchOperations.add,
306425
+ path: '/imports/-',
306426
+ value: {
306427
+ to: 'github_issue_label.this["' + label.name + '"]',
306428
+ id: `${this.cr.name}:${label.name}`,
306429
+ },
305673
306430
  });
305674
306431
  }
306432
+ return labelsToImport;
306433
+ }
306434
+ catch (err) {
306435
+ gh_provisioner_src_logger.error(`[gh-provisioner] ${fsGithubRepository.k8sId} error on importLabelsIfNeeded: ${err}`);
306436
+ throw new Error(`Error on importLabelsIfNeeded: ${err}`);
305675
306437
  }
305676
306438
  }
306439
+ function checkIfLabelExists(labelName, repoIssuesList) {
306440
+ return repoIssuesList.some((label) => label.name === labelName);
306441
+ }
305677
306442
 
305678
306443
  ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/helpers/index.ts
305679
306444
 
@@ -305682,6 +306447,7 @@ function provisionPermissions(fsGithubRepository) {
305682
306447
 
305683
306448
 
305684
306449
 
306450
+
305685
306451
  ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/post/additional_branches.ts
305686
306452
 
305687
306453
 
@@ -305746,6 +306512,7 @@ async function provisionRegularBranch(repo, branchName, sourceBranch, org) {
305746
306512
 
305747
306513
 
305748
306514
 
306515
+
305749
306516
  class EntityGHRepo extends base_Entity {
305750
306517
  constructor(artifact) {
305751
306518
  super(artifact, {
@@ -305754,18 +306521,24 @@ class EntityGHRepo extends base_Entity {
305754
306521
  variables: [],
305755
306522
  teams: [],
305756
306523
  collaborators: [],
306524
+ labels: [],
305757
306525
  },
305758
306526
  });
305759
306527
  }
305760
306528
  async loadResources(tfOp) {
306529
+ let repoAlreadyExists = false;
305761
306530
  try {
305762
306531
  this.synthMessage('Loading resources to Synth...');
306532
+ repoAlreadyExists = (await this.runWithGithubProvider(async () => {
306533
+ return await github.repo.repoExists(this.cr.spec.org, this.cr.name);
306534
+ }));
305763
306535
  await this.provisionRepository();
305764
306536
  await provisionDefaultBranch(this);
305765
306537
  await provisionCodeowners(this);
305766
306538
  await provisionVariables(this);
305767
306539
  await provisionOIDCSubjectClaim(this);
305768
306540
  await provisionPermissions(this);
306541
+ await provisionLabels(this, repoAlreadyExists);
305769
306542
  this.synthMessage('Synth finished');
305770
306543
  this.synthEnd(JSON.stringify(this.document, null, 2));
305771
306544
  }
@@ -306876,6 +307649,8 @@ function gh_checkrun_helperCreateCheckRunName(cmd, item) {
306876
307649
 
306877
307650
 
306878
307651
 
307652
+
307653
+ const process_operation_TF_CACHES_PATH = '/tmp/tfcaches';
306879
307654
  function process_operation_processOperation(item, op, handler) {
306880
307655
  operator_src_logger.info(`Processing operation ${op} on ${item.kind}/${item.metadata?.name}`);
306881
307656
  try {
@@ -307005,6 +307780,7 @@ async function* gh_process_operation_markedToDeletion(item, op, handler) {
307005
307780
  operator_src_logger.error(`PANIC!!: The Terraform process for item '${item.kind}/${item.metadata.name}' could not be killed`);
307006
307781
  }
307007
307782
  };
307783
+ opts.ctl['tfCacheDir'] = external_path_default().join(process_operation_TF_CACHES_PATH, `slot_${handler.getSlotInfo().slotId}`);
307008
307784
  const destroyOutput = await gh_provisioner.runGhProvisioner({
307009
307785
  mainCr: item,
307010
307786
  deps,
@@ -307153,6 +307929,7 @@ async function* process_operation_doApply(item, op, handler) {
307153
307929
  operator_src_logger.error(`PANIC!!: The Terraform process for item '${item.kind}/${item.metadata.name}' could not be killed`);
307154
307930
  }
307155
307931
  };
307932
+ opts.ctl['tfCacheDir'] = external_path_default().join(process_operation_TF_CACHES_PATH, `slot_${handler.getSlotInfo().slotId}`);
307156
307933
  const applyOutput = await gh_provisioner.runGhProvisioner({
307157
307934
  mainCr: item,
307158
307935
  deps,
@@ -307247,228 +308024,6 @@ async function* process_operation_doApply(item, op, handler) {
307247
308024
  }
307248
308025
  }
307249
308026
 
307250
- // EXTERNAL MODULE: ../operator/node_modules/@opentelemetry/exporter-prometheus/build/src/index.js
307251
- var build_src = __nccwpck_require__(35116);
307252
- // EXTERNAL MODULE: ../operator/node_modules/@opentelemetry/sdk-metrics/build/src/index.js
307253
- var sdk_metrics_build_src = __nccwpck_require__(30481);
307254
- ;// CONCATENATED MODULE: ../operator/src/metrics/CRStates.ts
307255
-
307256
-
307257
-
307258
-
307259
- const INTERVAL_IN_SEGS = 60;
307260
- class CRStateMetrics {
307261
- constructor(kind, namespace, meter) {
307262
- this.kind = kind;
307263
- this.provisionedGauge = meter.createGauge('firestartr_provisioned_total', {
307264
- description: 'Total number of CRs in PROVISIONED state',
307265
- });
307266
- this.provisioningGauge = meter.createGauge('firestartr_provisioning_total', {
307267
- description: 'Total number of CRs in PROVISIONING state',
307268
- });
307269
- this.outOfSyncGauge = meter.createGauge('firestartr_out_of_sync_total', {
307270
- description: 'Total number of CRs in OUT_OF_SYNC state',
307271
- });
307272
- this.errorGauge = meter.createGauge('firestartr_error_total', {
307273
- description: 'Total number of CRs in ERROR state',
307274
- });
307275
- this.planningGauge = meter.createGauge('firestartr_planning_total', {
307276
- description: 'Total number of CRs in PLANNING state',
307277
- });
307278
- this.deletedGauge = meter.createGauge('firestartr_deleted_total', {
307279
- description: 'Total number of CRs in DELETED state',
307280
- });
307281
- this.errorOnSyncGauge = meter.createGauge('firestartr_error_on_sync_total', {
307282
- description: 'Total number of CRs with failed SYNCs',
307283
- });
307284
- this.namespace = namespace;
307285
- }
307286
- async start() {
307287
- await this.__prepareConnection();
307288
- this.onUpdate = false;
307289
- this.updateInterval = setInterval(async () => {
307290
- await this.update();
307291
- }, 1000 * INTERVAL_IN_SEGS);
307292
- await this.update();
307293
- }
307294
- stop() {
307295
- if (this.updateInterval) {
307296
- clearInterval(this.updateInterval);
307297
- this.updateInterval = null;
307298
- }
307299
- }
307300
- async update() {
307301
- if (this.onUpdate)
307302
- return;
307303
- this.onUpdate = true;
307304
- try {
307305
- const items = await this.fListCRs();
307306
- let provisionedCount = 0;
307307
- let provisioningCount = 0;
307308
- let outOfSyncCount = 0;
307309
- let errorCount = 0;
307310
- let planningCount = 0;
307311
- let deletedCount = 0;
307312
- let errorOnSyncCount = 0;
307313
- for (const item of items) {
307314
- const status = item.status?.conditions.find((condition) => condition.type !== 'SYNCHRONIZED' && condition.status === 'True');
307315
- if (!status)
307316
- continue;
307317
- const syncCondition = item.status.conditions.find((condition) => {
307318
- return condition.type === 'SYNCHRONIZED';
307319
- });
307320
- if (syncCondition &&
307321
- syncCondition.message === SYNC_DEFAULT_ERROR_MESSAGE) {
307322
- errorOnSyncCount++;
307323
- }
307324
- switch (status.type) {
307325
- case 'PROVISIONED':
307326
- provisionedCount++;
307327
- break;
307328
- case 'PROVISIONING':
307329
- provisioningCount++;
307330
- break;
307331
- case 'OUT_OF_SYNC':
307332
- outOfSyncCount++;
307333
- break;
307334
- case 'PLANNING':
307335
- planningCount++;
307336
- break;
307337
- case 'DELETED':
307338
- deletedCount++;
307339
- break;
307340
- case 'ERROR':
307341
- errorCount++;
307342
- break;
307343
- }
307344
- }
307345
- this.provisionedGauge.record(provisionedCount, {
307346
- namespace: this.namespace,
307347
- kind: this.kind,
307348
- });
307349
- this.provisioningGauge.record(provisioningCount, {
307350
- namespace: this.namespace,
307351
- kind: this.kind,
307352
- });
307353
- this.planningGauge.record(planningCount, {
307354
- namespace: this.namespace,
307355
- kind: this.kind,
307356
- });
307357
- this.deletedGauge.record(deletedCount, {
307358
- namespace: this.namespace,
307359
- kind: this.kind,
307360
- });
307361
- this.outOfSyncGauge.record(outOfSyncCount, {
307362
- namespace: this.namespace,
307363
- kind: this.kind,
307364
- });
307365
- this.errorGauge.record(errorCount, {
307366
- namespace: this.namespace,
307367
- kind: this.kind,
307368
- });
307369
- this.errorOnSyncGauge.record(errorOnSyncCount, {
307370
- namespace: this.namespace,
307371
- kind: this.kind,
307372
- });
307373
- }
307374
- catch (err) {
307375
- console.log(`CRStateMetrics: update ${err}`);
307376
- this.onUpdate = false;
307377
- operator_src_logger.error(`On update of CR metrics: ${err}`);
307378
- await this.__prepareConnection();
307379
- }
307380
- this.onUpdate = false;
307381
- }
307382
- async __prepareConnection() {
307383
- const { kc, opts } = await ctl_getConnection();
307384
- const k8sApi = kc.makeApiClient(client_node_dist.CustomObjectsApi);
307385
- this.fListCRs = async () => {
307386
- try {
307387
- const response = await k8sApi.listNamespacedCustomObject('firestartr.dev', 'v1', this.namespace, this.kind);
307388
- const body = response.body;
307389
- return body.items;
307390
- }
307391
- catch (err) {
307392
- throw new Error(`On listing CRs ${this.kind}: ${err}`);
307393
- }
307394
- };
307395
- }
307396
- }
307397
-
307398
- ;// CONCATENATED MODULE: ../operator/src/metricsServer.ts
307399
-
307400
-
307401
-
307402
-
307403
- /* harmony default export */ async function metricsServer(kindList, namespace) {
307404
- const portFromEnv = process.env.METRICS_PORT;
307405
- const options = build_src/* PrometheusExporter.DEFAULT_OPTIONS */.rC.DEFAULT_OPTIONS;
307406
- options.port = portFromEnv ? parseInt(portFromEnv) : options.port;
307407
- options.endpoint = process.env.METRICS_ENDPOINT || options.endpoint;
307408
- const exporter = new build_src/* PrometheusExporter */.rC(options, () => {
307409
- console.log(`📊 Metrics endpoint: http://localhost:${options.port}${options.endpoint}`);
307410
- });
307411
- const attributes = { pid: process.pid };
307412
- void startMetrics(exporter, attributes, kindList, namespace);
307413
- }
307414
- async function startMetrics(exporter, attributes, kindList, namespace) {
307415
- const meterProvider = new sdk_metrics_build_src/* MeterProvider */.y0({
307416
- readers: [exporter],
307417
- });
307418
- const meter = meterProvider.getMeter('firestartr');
307419
- void startQueueMetrics(meter, attributes);
307420
- void startMemoryMetrics(meter, attributes);
307421
- void startCRStates(meter, kindList, namespace);
307422
- }
307423
- async function startQueueMetrics(meter, attributes) {
307424
- const nWorkItemsCounter = meter.createObservableGauge('firestartr_workitems_total', { description: 'Number of workitems in the queue' });
307425
- const nWorkItemsPendingCounter = meter.createObservableGauge('firestartr_workitems_pending_total', { description: 'Number of workitems with PENDING status in the queue' });
307426
- const nWorkItemsProcessingCounter = meter.createObservableGauge('firestartr_workitems_processing_total', { description: 'Number of workitems with PROCESSING status in the queue' });
307427
- const nWorkItemsFinishedCounter = meter.createObservableGauge('firestartr_workitems_finished_total', { description: 'Number of workitems with FINISHED status in the queue' });
307428
- const nWorkItemsInDeadLetterHandling = meter.createObservableGauge('firestartr_workitems_dead_letter_handling_total', {
307429
- description: 'Number of workitems of the queue handled by the Dead Letter Handler',
307430
- });
307431
- const queueMetrics = getQueueMetrics();
307432
- let total = queueMetrics.nItems;
307433
- let pending = queueMetrics.nItemsPending;
307434
- let processing = queueMetrics.nItemsProcessing;
307435
- let finished = queueMetrics.nItemsFinished;
307436
- let inDeadLetterHandling = queueMetrics.nItemsInDeadLetterHandling;
307437
- nWorkItemsCounter.addCallback((observer) => observer.observe(total, { ...attributes, ...queueMetrics.nItemsTypes }));
307438
- nWorkItemsPendingCounter.addCallback((observer) => observer.observe(pending, { ...attributes, ...queueMetrics.nItemsTypes }));
307439
- nWorkItemsProcessingCounter.addCallback((observer) => observer.observe(processing, {
307440
- ...attributes,
307441
- ...queueMetrics.nItemsTypes,
307442
- }));
307443
- nWorkItemsFinishedCounter.addCallback((observer) => observer.observe(finished, { ...attributes, ...queueMetrics.nItemsTypes }));
307444
- nWorkItemsInDeadLetterHandling.addCallback((observer) => observer.observe(inDeadLetterHandling, {
307445
- ...attributes,
307446
- ...queueMetrics.nItemsTypes,
307447
- }));
307448
- setInterval(() => {
307449
- const queueMetrics = getQueueMetrics();
307450
- total = queueMetrics.nItems;
307451
- pending = queueMetrics.nItemsPending;
307452
- processing = queueMetrics.nItemsProcessing;
307453
- finished = queueMetrics.nItemsFinished;
307454
- inDeadLetterHandling = queueMetrics.nItemsInDeadLetterHandling;
307455
- }, 1000);
307456
- }
307457
- async function startMemoryMetrics(meter, attributes) {
307458
- const usedMemoryCounter = meter.createObservableGauge('firestartr_used_memory', { description: 'Used memory in bytes' });
307459
- let heapUsage = process.memoryUsage().heapUsed;
307460
- usedMemoryCounter.addCallback((observer) => observer.observe(heapUsage, attributes));
307461
- setInterval(() => {
307462
- heapUsage = process.memoryUsage().heapUsed;
307463
- }, 1000);
307464
- }
307465
- async function startCRStates(meter, kindList, namespace) {
307466
- for (const kind of kindList) {
307467
- const crStateMetrics = new CRStateMetrics(kind, namespace, meter);
307468
- await crStateMetrics.start();
307469
- }
307470
- }
307471
-
307472
308027
  // EXTERNAL MODULE: external "util"
307473
308028
  var external_util_ = __nccwpck_require__(73837);
307474
308029
  ;// CONCATENATED MODULE: ../operator/src/cmd/tf_planner.ts
@@ -308182,9 +308737,9 @@ const crs_analyzerSubcommand = {
308182
308737
  };
308183
308738
 
308184
308739
  ;// CONCATENATED MODULE: ./package.json
308185
- const package_namespaceObject = JSON.parse('{"i8":"2.2.2-snapshot-0"}');
308740
+ const package_namespaceObject = JSON.parse('{"i8":"2.3.0-snapshot"}');
308186
308741
  ;// CONCATENATED MODULE: ../../package.json
308187
- const package_namespaceObject_1 = {"i8":"2.1.0"};
308742
+ const package_namespaceObject_1 = {"i8":"2.2.0"};
308188
308743
  ;// CONCATENATED MODULE: ./src/subcommands/index.ts
308189
308744
 
308190
308745