qontract-reconcile 0.10.1rc1169__py3-none-any.whl → 0.10.1rc1171__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc1169
3
+ Version: 0.10.1rc1171
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Home-page: https://github.com/app-sre/qontract-reconcile
6
6
  Author: Red Hat App-SRE Team
@@ -10,7 +10,7 @@ reconcile/aws_iam_password_reset.py,sha256=q96mwr2KeEQ5bpNniGlgIMZTxiuLSodcYfX-t
10
10
  reconcile/aws_support_cases_sos.py,sha256=hl_9L53yQYRQxKs3IWrd69Cc60XK067g_bJRM9B0udo,2975
11
11
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=O1wFp52EyF538c6txaWBs8eMtUIy19gyHZ6VzJ6QXS8,3512
12
12
  reconcile/checkpoint.py,sha256=_JhMxrye5BgkRMxWYuf7Upli6XayPINKSsuo3ynHTRc,5010
13
- reconcile/cli.py,sha256=-RT8biWI5mY9o6gYZ7HxXowePxmxbnfGKKqhMeUdiDc,107720
13
+ reconcile/cli.py,sha256=2W2_vnq68954f-lhNwX3yGgCrofwSbDtAk33b4TnicE,107467
14
14
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=rLh16BOlBOxTmJ8Si3wWyyEpmMlhh4Znx1Gc36qsmOc,4865
15
15
  reconcile/cluster_deployment_mapper.py,sha256=5gumAaRCcFXsabUJ1dnuUy9WrP_FEEM5JnOnE8ch9sE,2326
16
16
  reconcile/dashdotdb_base.py,sha256=l34QDu1G96_Ctnh7ZXdxXgSeCE93GQMdLAkWxmN6vDA,4775
@@ -46,7 +46,7 @@ reconcile/jenkins_roles.py,sha256=pNNYcnmyDCTVytG2mi3BFhq9A7_3l301oFRQtY_q6S8,44
46
46
  reconcile/jenkins_webhooks.py,sha256=K5h0OlCghoHlpot40IRq4BuezfKB1rj4Xk0dCUvqp3o,1952
47
47
  reconcile/jenkins_webhooks_cleaner.py,sha256=JsN_NVPfZJwv1JtSzZXDIHUqGiefL-DRffFnDGau9aY,1539
48
48
  reconcile/jenkins_worker_fleets.py,sha256=PMNGOX0krubFjInPiFT0za0KCiWBLEcVDuXdKRd1BrE,5378
49
- reconcile/jira_permissions_validator.py,sha256=t8FBjBJniRMODQNXfoHKst6qLDOzlua6hqy7rm7tmzs,13222
49
+ reconcile/jira_permissions_validator.py,sha256=GSjLwHrstuO4dGb9Oxfmg_9PLLreFsbJJ8WHhUM8FSY,13144
50
50
  reconcile/jira_watcher.py,sha256=L_UL2MKm2SoIGNsCLThm28pnqCkoFc154JWsD6bURug,3593
51
51
  reconcile/ldap_users.py,sha256=7hdO5CAPl-VNBvDRmKHg13LoblHXXPt7YEKNGomAoGg,3158
52
52
  reconcile/mr_client_gateway.py,sha256=WhjMd-sIXDFCV8-rt8CEjurJ5OYB1pOD0K3o0tZRXQg,1885
@@ -161,7 +161,7 @@ reconcile/aws_version_sync/merge_request_manager/merge_request_manager.py,sha256
161
161
  reconcile/change_owners/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
162
162
  reconcile/change_owners/approver.py,sha256=Z3_11vnK2WNOxjEEXVDh0224-_-qbt9d6mBeVE-7fsc,2259
163
163
  reconcile/change_owners/bundle.py,sha256=h30fU-JmLH5a-rCAovpzTeTkkkgZztsZ5A2raee0YuU,5355
164
- reconcile/change_owners/change_log_tracking.py,sha256=JzUmW6m_qg43r0a6WI__YX8-j_ejt1peIy6CkZJfZTg,9049
164
+ reconcile/change_owners/change_log_tracking.py,sha256=Mw8fuRmMYOEE4qfHJV5fbIjYjVsBw0WCWkIPMfkesMc,9137
165
165
  reconcile/change_owners/change_owners.py,sha256=0HRJhDm0oW3uYJFgzynqA1gA0lbhalhSkmWOiQmr-NM,17062
166
166
  reconcile/change_owners/change_types.py,sha256=YFqykx1I71wcUHEK_eDqPjEeQpPVV4bjqT9W3LgqNFw,32073
167
167
  reconcile/change_owners/changes.py,sha256=o4vylcFhu75hltJDghadm2qkpTXyMGiDeEw-jxl7pLI,18226
@@ -525,7 +525,7 @@ reconcile/test/test_gitlab_permissions.py,sha256=aMf5SUeVp-aQ1bWGQPQLYa85auzRlyf
525
525
  reconcile/test/test_instrumented_wrappers.py,sha256=CZzhnQH0c4i7-Rxjg7-0dfFMvVPegLHL46z5NHOOCwo,608
526
526
  reconcile/test/test_integrations_manager.py,sha256=xpyQAVz57wAbovrcQzAeuyq8VzdItUyW2d2kp1WW_5c,38184
527
527
  reconcile/test/test_jenkins_worker_fleets.py,sha256=o1jlT7OBBSgu0M3iI4xMdz_x6SciF7yhNBpLk5gTJfg,2361
528
- reconcile/test/test_jira_permissions_validator.py,sha256=zhtAL97IkCyY9R29fDRvDCE1z9S7OVQV7gqu-7Vo5-4,16279
528
+ reconcile/test/test_jira_permissions_validator.py,sha256=OA3hcnsH0eYXA4khJO6d1PE7zo2f0vYg73PrCqTzkyo,17646
529
529
  reconcile/test/test_jump_host.py,sha256=EeHMhT5rTZgx__R_29mtCBWt-NZCcKsQ6CR-B3xmCps,3284
530
530
  reconcile/test/test_ldap_users.py,sha256=_clylG-Qfes8yNb9T3CMaDQovLwyVZlTfJgRtGHARgE,4080
531
531
  reconcile/test/test_make.py,sha256=zTdjgq-3idFlec_0qJenk9wWw0QMLvSpJfPsptXmync,677
@@ -687,7 +687,7 @@ reconcile/utils/helpers.py,sha256=womAD2bKPUAFOjHvNPAe_2Hsb-oVTxuQiYPGeR-Thp0,17
687
687
  reconcile/utils/imap_client.py,sha256=h8YDiCSCvroErhpH_-KGYI7Y2WU2Q2oSpuxDFbOkSbY,1989
688
688
  reconcile/utils/instrumented_wrappers.py,sha256=eVwMoa6FCrYxLv3RML3WpZF9qKVfCTjMxphgVXG03OM,1073
689
689
  reconcile/utils/jenkins_api.py,sha256=RaKuZmO7_lbI-hE6c_Pq2a6CQdmBVj7BcP2jR68cIbI,7081
690
- reconcile/utils/jira_client.py,sha256=VdWJfI9VAK2ZtYHmvuqL635t0S5eYtmBY6zR8R-qr-s,7775
690
+ reconcile/utils/jira_client.py,sha256=oWi7rcAP1C59oIBTPg6kRntI25Zm4e7FyvdVYvZ9RZ8,7881
691
691
  reconcile/utils/jjb_client.py,sha256=9Aw4SfV4pBNW5Kj7dGZwakUlwsWuqtAAiSD9o1F4AZA,14524
692
692
  reconcile/utils/jsonpath.py,sha256=wdxOMqR-GMpQf5vRPWRMqAF7bCiXDBkkcFfY2U4j_tk,5536
693
693
  reconcile/utils/jump_host.py,sha256=svtWy64zZAx7XYY62vu580KgwdatmHjX4-BdxEl58Uw,5158
@@ -880,8 +880,8 @@ tools/test/test_qontract_cli.py,sha256=iuzKbQ6ahinvjoQmQLBrG4shey0z-1rB6qCgS8T6d
880
880
  tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
881
881
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
882
882
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
883
- qontract_reconcile-0.10.1rc1169.dist-info/METADATA,sha256=YlzW8en2-M_JB_DAj-tB1zQc-cJ4E3uA7a3D34mIifg,2213
884
- qontract_reconcile-0.10.1rc1169.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
885
- qontract_reconcile-0.10.1rc1169.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
886
- qontract_reconcile-0.10.1rc1169.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
887
- qontract_reconcile-0.10.1rc1169.dist-info/RECORD,,
883
+ qontract_reconcile-0.10.1rc1171.dist-info/METADATA,sha256=GxrtQ-z9G_ZC9ukO5-h9-1qjrqcK-lkkr8bjunKNfjQ,2213
884
+ qontract_reconcile-0.10.1rc1171.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
885
+ qontract_reconcile-0.10.1rc1171.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
886
+ qontract_reconcile-0.10.1rc1171.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
887
+ qontract_reconcile-0.10.1rc1171.dist-info/RECORD,,
@@ -40,6 +40,7 @@ class ChangeLogItem:
40
40
  change_types: list[str] = field(default_factory=list)
41
41
  error: bool = False
42
42
  apps: list[str] = field(default_factory=list)
43
+ description: str = ""
43
44
 
44
45
 
45
46
  @dataclass
@@ -136,6 +137,7 @@ class ChangeLogIntegration(QontractReconcileIntegration[ChangeLogIntegrationPara
136
137
  change_log_item = ChangeLogItem(
137
138
  commit=commit,
138
139
  merged_at=merged_at,
140
+ description=gl_commit.message.split("\n")[2],
139
141
  )
140
142
  change_log.items.append(change_log_item)
141
143
  obj = diff_state.get(key, None)
reconcile/cli.py CHANGED
@@ -1158,18 +1158,12 @@ def jenkins_webhooks_cleaner(ctx):
1158
1158
 
1159
1159
 
1160
1160
  @integration.command(short_help="Validate permissions in Jira.")
1161
- @click.option(
1162
- "--exit-on-permission-errors/--no-exit-on-permission-errors",
1163
- help="Throw and error in case of board permission errors. Useful for PR checks.",
1164
- default=True,
1165
- )
1166
1161
  @enable_extended_early_exit
1167
1162
  @extended_early_exit_cache_ttl_seconds
1168
1163
  @log_cached_log_output
1169
1164
  @click.pass_context
1170
1165
  def jira_permissions_validator(
1171
1166
  ctx,
1172
- exit_on_permission_errors,
1173
1167
  enable_extended_early_exit,
1174
1168
  extended_early_exit_cache_ttl_seconds,
1175
1169
  log_cached_log_output,
@@ -1179,7 +1173,6 @@ def jira_permissions_validator(
1179
1173
  run_integration(
1180
1174
  reconcile.jira_permissions_validator,
1181
1175
  ctx.obj,
1182
- exit_on_permission_errors,
1183
1176
  enable_extended_early_exit=enable_extended_early_exit,
1184
1177
  extended_early_exit_cache_ttl_seconds=extended_early_exit_cache_ttl_seconds,
1185
1178
  log_cached_log_output=log_cached_log_output,
@@ -61,11 +61,12 @@ class ValidationError(IntFlag):
61
61
  PERMISSION_ERROR = auto()
62
62
  PUBLIC_PROJECT_NO_SECURITY_LEVEL = auto()
63
63
  INVALID_COMPONENT = auto()
64
+ PROJECT_ARCHIVED = auto()
64
65
 
65
66
 
66
67
  class RunnerParams(TypedDict):
67
- exit_on_permission_errors: bool
68
68
  boards: list[JiraBoardV1]
69
+ dry_run: bool
69
70
 
70
71
 
71
72
  class CacheSource(TypedDict):
@@ -82,6 +83,10 @@ def board_is_valid(
82
83
  ) -> ValidationError:
83
84
  error = ValidationError(0)
84
85
  try:
86
+ if jira.is_archived:
87
+ logging.error(f"[{board.name}] project is archived")
88
+ return ValidationError.PROJECT_ARCHIVED
89
+
85
90
  if not jira.can_create_issues():
86
91
  logging.error(f"[{board.name}] can not create issues in project")
87
92
  error |= ValidationError.CANT_CREATE_ISSUE
@@ -196,11 +201,11 @@ def board_is_valid(
196
201
  def validate_boards(
197
202
  metrics_container: metrics.MetricsContainer,
198
203
  secret_reader: SecretReaderBase,
199
- exit_on_permission_errors: bool,
200
204
  jira_client_settings: JiraWatcherSettings | None,
201
205
  jira_boards: Iterable[JiraBoardV1],
202
206
  default_issue_type: str,
203
207
  default_reopen_state: str,
208
+ dry_run: bool,
204
209
  jira_client_class: type[JiraClient] = JiraClient,
205
210
  ) -> bool:
206
211
  error = False
@@ -238,15 +243,15 @@ def validate_boards(
238
243
  ),
239
244
  value=1,
240
245
  )
241
- # don't fail during PR checks at the moment
242
- # this make the transistion to the new integration behaviour much smoother
243
- if exit_on_permission_errors:
246
+ if dry_run:
247
+ # throw an error for MR checks but not in prod mode
244
248
  error = True
245
249
  case (
246
- ValidationError.PERMISSION_ERROR | ValidationError.CANT_CREATE_ISSUE
250
+ ValidationError.CANT_CREATE_ISSUE | ValidationError.PROJECT_ARCHIVED
247
251
  ):
248
- # we can't create jira tickets, and we don't have all needed the permissions
249
- error = True
252
+ if dry_run:
253
+ # throw an error for MR checks but not in prod mode
254
+ error = True
250
255
  case _:
251
256
  error = True
252
257
  except Exception as e:
@@ -269,7 +274,6 @@ def export_boards(boards: list[JiraBoardV1]) -> list[dict]:
269
274
 
270
275
  def run(
271
276
  dry_run: bool,
272
- exit_on_permission_errors: bool,
273
277
  enable_extended_early_exit: bool = False,
274
278
  extended_early_exit_cache_ttl_seconds: int = 3600,
275
279
  log_cached_log_output: bool = False,
@@ -277,8 +281,8 @@ def run(
277
281
  gql_api = gql.get_api()
278
282
  boards = get_jira_boards(query_func=gql_api.query)
279
283
  runner_params: RunnerParams = {
280
- "exit_on_permission_errors": exit_on_permission_errors,
281
284
  "boards": boards,
285
+ "dry_run": dry_run,
282
286
  }
283
287
  if enable_extended_early_exit and get_feature_toggle_state(
284
288
  "jira-permissions-validator-extended-early-exit",
@@ -308,9 +312,7 @@ def run(
308
312
  runner(**runner_params)
309
313
 
310
314
 
311
- def runner(
312
- exit_on_permission_errors: bool, boards: list[JiraBoardV1]
313
- ) -> ExtendedEarlyExitRunnerResult:
315
+ def runner(boards: list[JiraBoardV1], dry_run: bool) -> ExtendedEarlyExitRunnerResult:
314
316
  gql_api = gql.get_api()
315
317
  settings = get_jira_settings(gql_api=gql_api)
316
318
  jiralert_settings = get_jiralert_settings(query_func=gql_api.query)
@@ -321,11 +323,11 @@ def runner(
321
323
  error = validate_boards(
322
324
  metrics_container=metrics_container,
323
325
  secret_reader=secret_reader,
324
- exit_on_permission_errors=exit_on_permission_errors,
325
326
  jira_client_settings=settings.jira_watcher,
326
327
  jira_boards=boards,
327
328
  default_issue_type=jiralert_settings.default_issue_type,
328
329
  default_reopen_state=jiralert_settings.default_reopen_state,
330
+ dry_run=dry_run,
329
331
  )
330
332
 
331
333
  if error:
@@ -93,7 +93,7 @@ def test_jira_permissions_validator_get_jira_boards(
93
93
 
94
94
 
95
95
  @pytest.mark.parametrize(
96
- "board_is_valid, exit_on_permission_errors, error_returned, metric_set",
96
+ "board_is_valid, dry_run, error_returned, metric_set",
97
97
  [
98
98
  (0, True, False, False),
99
99
  (ValidationError.CANT_CREATE_ISSUE, True, True, False),
@@ -104,19 +104,11 @@ def test_jira_permissions_validator_get_jira_boards(
104
104
  (ValidationError.INVALID_PRIORITY, True, True, False),
105
105
  (ValidationError.PUBLIC_PROJECT_NO_SECURITY_LEVEL, True, True, False),
106
106
  (ValidationError.PERMISSION_ERROR, True, True, True),
107
- # special case: CANT_CREATE_ISSUE and PERMISSION_ERROR
108
- (
109
- ValidationError.CANT_CREATE_ISSUE | ValidationError.PERMISSION_ERROR,
110
- True,
111
- True,
112
- False,
113
- ),
114
- (
115
- ValidationError.CANT_CREATE_ISSUE | ValidationError.PERMISSION_ERROR,
116
- False,
117
- True,
118
- False,
119
- ),
107
+ (ValidationError.PROJECT_ARCHIVED, True, True, False),
108
+ # no dry-run
109
+ (ValidationError.CANT_CREATE_ISSUE, False, False, False),
110
+ (ValidationError.PERMISSION_ERROR, False, False, True),
111
+ (ValidationError.PROJECT_ARCHIVED, False, False, False),
120
112
  # test with another error
121
113
  (
122
114
  ValidationError.INVALID_PRIORITY | ValidationError.PERMISSION_ERROR,
@@ -137,7 +129,7 @@ def test_jira_permissions_validator_validate_boards(
137
129
  boards: list[JiraBoardV1],
138
130
  secret_reader: Mock,
139
131
  board_is_valid: ValidationError,
140
- exit_on_permission_errors: bool,
132
+ dry_run: bool,
141
133
  error_returned: bool,
142
134
  metric_set: bool,
143
135
  ) -> None:
@@ -151,11 +143,11 @@ def test_jira_permissions_validator_validate_boards(
151
143
  validate_boards(
152
144
  metrics_container=metrics_container_mock,
153
145
  secret_reader=secret_reader,
154
- exit_on_permission_errors=exit_on_permission_errors,
155
146
  jira_client_settings=None,
156
147
  jira_boards=boards,
157
148
  default_issue_type="task",
158
149
  default_reopen_state="new",
150
+ dry_run=dry_run,
159
151
  jira_client_class=jira_client_class,
160
152
  )
161
153
  == error_returned
@@ -192,6 +184,7 @@ def test_jira_permissions_validator_board_is_valid_happy_path(
192
184
  },
193
185
  )
194
186
  jira_client = mocker.create_autospec(spec=JiraClient)
187
+ jira_client.is_archived = False
195
188
  jira_client.can_create_issues.return_value = True
196
189
  jira_client.can_transition_issues.return_value = True
197
190
  jira_client.project_issue_types.return_value = [
@@ -239,6 +232,7 @@ def test_jira_permissions_validator_board_is_valid_all_errors(
239
232
  },
240
233
  )
241
234
  jira_client = mocker.create_autospec(spec=JiraClient)
235
+ jira_client.is_archived = False
242
236
  jira_client.can_create_issues.return_value = False
243
237
  jira_client.can_transition_issues.return_value = False
244
238
  jira_client.project_issue_types.return_value = []
@@ -290,6 +284,7 @@ def test_jira_permissions_validator_board_is_valid_bad_issue_status(
290
284
  },
291
285
  )
292
286
  jira_client = mocker.create_autospec(spec=JiraClient)
287
+ jira_client.is_archived = False
293
288
  jira_client.can_create_issues.return_value = True
294
289
  jira_client.can_transition_issues.return_value = True
295
290
  jira_client.project_issue_types.return_value = [
@@ -340,6 +335,7 @@ def test_jira_permissions_validator_board_is_valid_public_project(
340
335
  },
341
336
  )
342
337
  jira_client = mocker.create_autospec(spec=JiraClient)
338
+ jira_client.is_archived = False
343
339
  jira_client.can_create_issues.return_value = True
344
340
  jira_client.can_transition_issues.return_value = True
345
341
  jira_client.project_issue_types.return_value = [
@@ -390,6 +386,7 @@ def test_jira_permissions_validator_board_is_valid_permission_error(
390
386
  },
391
387
  )
392
388
  jira_client = mocker.create_autospec(spec=JiraClient)
389
+ jira_client.is_archived = False
393
390
  jira_client.can_create_issues.side_effect = JIRAError(status_code=403)
394
391
  assert (
395
392
  board_is_valid(
@@ -430,6 +427,7 @@ def test_jira_permissions_validator_board_is_valid_exception(
430
427
  },
431
428
  )
432
429
  jira_client = mocker.create_autospec(spec=JiraClient)
430
+ jira_client.is_archived = False
433
431
  jira_client.can_create_issues.side_effect = JIRAError(status_code=500)
434
432
  with pytest.raises(JIRAError):
435
433
  board_is_valid(
@@ -468,6 +466,7 @@ def test_jira_permissions_validator_board_is_valid_exception_401(
468
466
  },
469
467
  )
470
468
  jira_client = mocker.create_autospec(spec=JiraClient)
469
+ jira_client.is_archived = False
471
470
  jira_client.can_create_issues.side_effect = JIRAError(status_code=401)
472
471
  # no error for 401
473
472
  board_is_valid(
@@ -478,3 +477,43 @@ def test_jira_permissions_validator_board_is_valid_exception_401(
478
477
  jira_server_priorities={"Minor": "1", "Major": "2", "Critical": "3"},
479
478
  public_projects=[],
480
479
  )
480
+
481
+
482
+ def test_jira_permissions_validator_board_is_valid_archived(
483
+ mocker: MockerFixture, gql_class_factory: Callable
484
+ ) -> None:
485
+ board = gql_class_factory(
486
+ JiraBoardV1,
487
+ {
488
+ "name": "jira-board-default",
489
+ "server": {
490
+ "serverUrl": "https://jira-server.com",
491
+ "token": {"path": "vault/path/token", "field": "token"},
492
+ },
493
+ "issueType": "bug",
494
+ "issueResolveState": "Closed",
495
+ "issueReopenState": "Open",
496
+ "issueSecurityId": "32168",
497
+ "severityPriorityMappings": {
498
+ "name": "major-major",
499
+ "mappings": [
500
+ {"priority": "Minor"},
501
+ {"priority": "Major"},
502
+ {"priority": "Critical"},
503
+ ],
504
+ },
505
+ },
506
+ )
507
+ jira_client = mocker.create_autospec(spec=JiraClient)
508
+ jira_client.is_archived = True
509
+ assert (
510
+ board_is_valid(
511
+ jira=jira_client,
512
+ board=board,
513
+ default_issue_type="task",
514
+ default_reopen_state="new",
515
+ jira_server_priorities={"Minor": "1", "Major": "2", "Critical": "3"},
516
+ public_projects=[],
517
+ )
518
+ == ValidationError.PROJECT_ARCHIVED
519
+ )
@@ -241,3 +241,7 @@ class JiraClient:
241
241
  def components(self) -> list[str]:
242
242
  """Return a list of all components for the project."""
243
243
  return [c.name for c in self.jira.project_components(self.project)]
244
+
245
+ @property
246
+ def is_archived(self) -> bool:
247
+ return self.jira.project(self.project).archived