qontract-reconcile 0.10.2.dev60__py3-none-any.whl → 0.10.2.dev61__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.
tools/qontract_cli.py CHANGED
@@ -10,6 +10,7 @@ import sys
10
10
  import tempfile
11
11
  import textwrap
12
12
  from collections import defaultdict
13
+ from collections.abc import Callable, Mapping
13
14
  from datetime import (
14
15
  UTC,
15
16
  datetime,
@@ -54,7 +55,6 @@ from reconcile.change_owners.bundle import NoOpFileDiffResolver
54
55
  from reconcile.change_owners.change_log_tracking import (
55
56
  BUNDLE_DIFFS_OBJ,
56
57
  ChangeLog,
57
- ChangeLogItem,
58
58
  )
59
59
  from reconcile.change_owners.change_owners import (
60
60
  fetch_change_type_processors,
@@ -81,6 +81,7 @@ from reconcile.gql_definitions.app_sre_tekton_access_revalidation.roles import (
81
81
  from reconcile.gql_definitions.common.app_interface_vault_settings import (
82
82
  AppInterfaceSettingsV1,
83
83
  )
84
+ from reconcile.gql_definitions.common.clusters import ClusterSpecROSAV1
84
85
  from reconcile.gql_definitions.fragments.aus_organization import AUSOCMOrganization
85
86
  from reconcile.gql_definitions.integrations import integrations as integrations_gql
86
87
  from reconcile.gql_definitions.maintenance import maintenances as maintenances_gql
@@ -152,6 +153,7 @@ from reconcile.utils.oc_map import (
152
153
  init_oc_map_from_clusters,
153
154
  )
154
155
  from reconcile.utils.ocm import OCM_PRODUCT_ROSA, OCMMap
156
+ from reconcile.utils.ocm.upgrades import get_upgrade_policies
155
157
  from reconcile.utils.ocm_base_client import init_ocm_base_client
156
158
  from reconcile.utils.output import print_output
157
159
  from reconcile.utils.saasherder.models import TargetSpec
@@ -190,7 +192,7 @@ else:
190
192
  CopySourceTypeDef = object
191
193
 
192
194
 
193
- def output(function):
195
+ def output(function: Callable) -> Callable:
194
196
  function = click.option(
195
197
  "--output",
196
198
  "-o",
@@ -201,14 +203,14 @@ def output(function):
201
203
  return function
202
204
 
203
205
 
204
- def sort(function):
206
+ def sort(function: Callable) -> Callable:
205
207
  function = click.option(
206
208
  "--sort", "-s", help="sort output", default=True, type=bool
207
209
  )(function)
208
210
  return function
209
211
 
210
212
 
211
- def to_string(function):
213
+ def to_string(function: Callable) -> Callable:
212
214
  function = click.option(
213
215
  "--to-string", help="stringify output", default=False, type=bool
214
216
  )(function)
@@ -218,14 +220,14 @@ def to_string(function):
218
220
  @click.group()
219
221
  @config_file
220
222
  @click.pass_context
221
- def root(ctx, configfile):
223
+ def root(ctx: click.Context, configfile: str) -> None:
222
224
  ctx.ensure_object(dict)
223
225
  config.init_from_toml(configfile)
224
226
  gql.init_from_config()
225
227
 
226
228
 
227
229
  @root.result_callback()
228
- def exit_cli(ctx, configfile):
230
+ def exit_cli(ctx: click.Context, configfile: str) -> None:
229
231
  GqlApiSingleton.close()
230
232
 
231
233
 
@@ -234,7 +236,7 @@ def exit_cli(ctx, configfile):
234
236
  @sort
235
237
  @to_string
236
238
  @click.pass_context
237
- def get(ctx, output, sort, to_string):
239
+ def get(ctx: click.Context, output: str, sort: bool, to_string: bool) -> None:
238
240
  ctx.obj["options"] = {
239
241
  "output": output,
240
242
  "sort": sort,
@@ -245,7 +247,7 @@ def get(ctx, output, sort, to_string):
245
247
  @root.group()
246
248
  @output
247
249
  @click.pass_context
248
- def describe(ctx, output):
250
+ def describe(ctx: click.Context, output: str) -> None:
249
251
  ctx.obj["options"] = {
250
252
  "output": output,
251
253
  }
@@ -253,7 +255,7 @@ def describe(ctx, output):
253
255
 
254
256
  @get.command()
255
257
  @click.pass_context
256
- def settings(ctx):
258
+ def settings(ctx: click.Context) -> None:
257
259
  settings = queries.get_app_interface_settings()
258
260
  columns = ["vault", "kubeBinary", "mergeRequestGateway"]
259
261
  print_output(ctx.obj["options"], [settings], columns)
@@ -262,7 +264,7 @@ def settings(ctx):
262
264
  @get.command()
263
265
  @click.argument("name", default="")
264
266
  @click.pass_context
265
- def aws_accounts(ctx, name):
267
+ def aws_accounts(ctx: click.Context, name: str) -> None:
266
268
  accounts = queries.get_aws_accounts(name=name)
267
269
  if not accounts:
268
270
  print("no aws accounts found")
@@ -274,7 +276,7 @@ def aws_accounts(ctx, name):
274
276
  @get.command()
275
277
  @click.argument("name", default="")
276
278
  @click.pass_context
277
- def clusters(ctx, name):
279
+ def clusters(ctx: click.Context, name: str) -> None:
278
280
  clusters = queries.get_clusters()
279
281
  if name:
280
282
  clusters = [c for c in clusters if c["name"] == name]
@@ -291,7 +293,7 @@ def clusters(ctx, name):
291
293
  @get.command()
292
294
  @click.argument("name", default="")
293
295
  @click.pass_context
294
- def cluster_upgrades(ctx, name):
296
+ def cluster_upgrades(ctx: click.Context, name: str) -> None:
295
297
  settings = queries.get_app_interface_settings()
296
298
 
297
299
  clusters = queries.get_clusters()
@@ -322,12 +324,11 @@ def cluster_upgrades(ctx, name):
322
324
  if data.get("upgradePolicy") == "automatic":
323
325
  data["schedule"] = c["upgradePolicy"]["schedule"]
324
326
  ocm = ocm_map.get(c["name"])
325
- if ocm:
326
- upgrade_policy = ocm.get_upgrade_policies(c["name"])
327
- if upgrade_policy and len(upgrade_policy) > 0:
328
- next_run = upgrade_policy[0].get("next_run")
329
- if next_run:
330
- data["next_run"] = next_run
327
+ upgrade_policy = get_upgrade_policies(ocm.ocm_api, c["spec"]["id"])
328
+ if upgrade_policy and len(upgrade_policy) > 0:
329
+ next_run = upgrade_policy[0].get("next_run")
330
+ if next_run:
331
+ data["next_run"] = next_run
331
332
  else:
332
333
  data["upgradePolicy"] = "manual"
333
334
 
@@ -341,7 +342,7 @@ def cluster_upgrades(ctx, name):
341
342
  @get.command()
342
343
  @environ(["APP_INTERFACE_STATE_BUCKET", "APP_INTERFACE_STATE_BUCKET_ACCOUNT"])
343
344
  @click.pass_context
344
- def version_history(ctx):
345
+ def version_history(ctx: click.Context) -> None:
345
346
  import reconcile.aus.ocm_upgrade_scheduler as ous
346
347
 
347
348
  clusters = aus_clusters_query(query_func=gql.get_api().query).clusters or []
@@ -377,11 +378,11 @@ def version_history(ctx):
377
378
 
378
379
  def get_upgrade_policies_data(
379
380
  org_upgrade_specs: list[OrganizationUpgradeSpec],
380
- md_output,
381
- integration,
382
- workload=None,
383
- show_only_soaking_upgrades=False,
384
- by_workload=False,
381
+ md_output: bool,
382
+ integration: str,
383
+ workload: str | None = None,
384
+ show_only_soaking_upgrades: bool = False,
385
+ by_workload: bool = False,
385
386
  ) -> list:
386
387
  if not org_upgrade_specs:
387
388
  return []
@@ -562,12 +563,12 @@ more than 6 hours will be highlighted.
562
563
  )
563
564
  @click.pass_context
564
565
  def cluster_upgrade_policies(
565
- ctx,
566
- cluster=None,
567
- workload=None,
568
- show_only_soaking_upgrades=False,
569
- by_workload=False,
570
- ):
566
+ ctx: click.Context,
567
+ cluster: str | None = None,
568
+ workload: str | None = None,
569
+ show_only_soaking_upgrades: bool = False,
570
+ by_workload: bool = False,
571
+ ) -> None:
571
572
  print(
572
573
  "https://grafana.app-sre.devshift.net/d/ukLXCSwVz/aus-cluster-upgrade-overview"
573
574
  )
@@ -582,9 +583,7 @@ def inherit_version_data_text(org: AUSOCMOrganization) -> str:
582
583
 
583
584
  @get.command()
584
585
  @click.pass_context
585
- def ocm_fleet_upgrade_policies(
586
- ctx,
587
- ):
586
+ def ocm_fleet_upgrade_policies(ctx: click.Context) -> None:
588
587
  from reconcile.aus.ocm_upgrade_scheduler_org import (
589
588
  OCMClusterUpgradeSchedulerOrgIntegration,
590
589
  )
@@ -617,7 +616,12 @@ def ocm_fleet_upgrade_policies(
617
616
  help="Ignore STS clusters",
618
617
  )
619
618
  @click.pass_context
620
- def aus_fleet_upgrade_policies(ctx, ocm_env, ocm_org_ids, ignore_sts_clusters):
619
+ def aus_fleet_upgrade_policies(
620
+ ctx: click.Context,
621
+ ocm_env: str | None,
622
+ ocm_org_ids: str | None,
623
+ ignore_sts_clusters: bool,
624
+ ) -> None:
621
625
  from reconcile.aus.advanced_upgrade_service import AdvancedUpgradeServiceIntegration
622
626
 
623
627
  parsed_ocm_org_ids = set(ocm_org_ids.split(",")) if ocm_org_ids else None
@@ -634,8 +638,8 @@ def aus_fleet_upgrade_policies(ctx, ocm_env, ocm_org_ids, ignore_sts_clusters):
634
638
 
635
639
 
636
640
  def generate_fleet_upgrade_policices_report(
637
- ctx, aus_integration: AdvancedUpgradeSchedulerBaseIntegration
638
- ):
641
+ ctx: click.Context, aus_integration: AdvancedUpgradeSchedulerBaseIntegration
642
+ ) -> None:
639
643
  md_output = ctx.obj["options"]["output"] == "md"
640
644
 
641
645
  org_upgrade_specs: dict[str, OrganizationUpgradeSpec] = {}
@@ -953,7 +957,7 @@ def upgrade_cluster_addon(
953
957
  )
954
958
 
955
959
 
956
- def has_cluster_account_access(cluster: dict[str, Any]):
960
+ def has_cluster_account_access(cluster: dict[str, Any]) -> bool:
957
961
  spec = cluster.get("spec") or {}
958
962
  account = spec.get("account")
959
963
  return account or cluster.get("awsInfrastructureManagementAccounts") is not None
@@ -962,7 +966,7 @@ def has_cluster_account_access(cluster: dict[str, Any]):
962
966
  @get.command()
963
967
  @click.argument("name", default="")
964
968
  @click.pass_context
965
- def clusters_network(ctx, name):
969
+ def clusters_network(ctx: click.Context, name: str) -> None:
966
970
  settings = queries.get_app_interface_settings()
967
971
  clusters = [
968
972
  c
@@ -1025,7 +1029,7 @@ def clusters_network(ctx, name):
1025
1029
 
1026
1030
  @get.command()
1027
1031
  @click.pass_context
1028
- def network_reservations(ctx) -> None:
1032
+ def network_reservations(ctx: click.Context) -> None:
1029
1033
  from reconcile.typed_queries.reserved_networks import get_networks
1030
1034
 
1031
1035
  columns = [
@@ -1038,11 +1042,10 @@ def network_reservations(ctx) -> None:
1038
1042
  ]
1039
1043
  network_table = []
1040
1044
 
1041
- def md_link(url) -> str:
1045
+ def md_link(url: str) -> str:
1042
1046
  if ctx.obj["options"]["output"] == "md":
1043
1047
  return f"[{url}]({url})"
1044
- else:
1045
- return url
1048
+ return url
1046
1049
 
1047
1050
  for network in get_networks():
1048
1051
  parentAddress = "none"
@@ -1083,7 +1086,7 @@ def network_reservations(ctx) -> None:
1083
1086
  default=24,
1084
1087
  )
1085
1088
  @click.pass_context
1086
- def cidr_blocks(ctx, for_cluster: int, mask: int) -> None:
1089
+ def cidr_blocks(ctx: click.Context, for_cluster: int, mask: int) -> None:
1087
1090
  import ipaddress
1088
1091
 
1089
1092
  from reconcile.typed_queries.aws_vpcs import get_aws_vpcs
@@ -1209,7 +1212,7 @@ def ocm_aws_infrastructure_access_switch_role_links_data() -> list[dict]:
1209
1212
 
1210
1213
  @get.command()
1211
1214
  @click.pass_context
1212
- def ocm_aws_infrastructure_access_switch_role_links_flat(ctx):
1215
+ def ocm_aws_infrastructure_access_switch_role_links_flat(ctx: click.Context) -> None:
1213
1216
  results = ocm_aws_infrastructure_access_switch_role_links_data()
1214
1217
  columns = ["cluster", "user_arn", "access_level", "switch_role_link"]
1215
1218
  print_output(ctx.obj["options"], results, columns)
@@ -1217,11 +1220,11 @@ def ocm_aws_infrastructure_access_switch_role_links_flat(ctx):
1217
1220
 
1218
1221
  @get.command()
1219
1222
  @click.pass_context
1220
- def ocm_aws_infrastructure_access_switch_role_links(ctx):
1223
+ def ocm_aws_infrastructure_access_switch_role_links(ctx: click.Context) -> None:
1221
1224
  if ctx.obj["options"]["output"] != "md":
1222
1225
  raise Exception(f"Unupported output: {ctx.obj['options']['output']}")
1223
1226
  results = ocm_aws_infrastructure_access_switch_role_links_data()
1224
- by_user = {}
1227
+ by_user: dict = {}
1225
1228
  for r in results:
1226
1229
  by_user.setdefault(r["user"], []).append(r)
1227
1230
  columns = ["cluster", "source_login", "access_level", "switch_role_link"]
@@ -1235,7 +1238,7 @@ def ocm_aws_infrastructure_access_switch_role_links(ctx):
1235
1238
 
1236
1239
  @get.command()
1237
1240
  @click.pass_context
1238
- def clusters_aws_account_ids(ctx):
1241
+ def clusters_aws_account_ids(ctx: click.Context) -> None:
1239
1242
  settings = queries.get_app_interface_settings()
1240
1243
  clusters = [c for c in queries.get_clusters() if c.get("ocm") is not None]
1241
1244
  ocm_map = OCMMap(clusters=clusters, settings=settings)
@@ -1265,7 +1268,7 @@ def clusters_aws_account_ids(ctx):
1265
1268
  @root.command()
1266
1269
  @click.argument("account_name")
1267
1270
  @click.pass_context
1268
- def user_credentials_migrate_output(ctx, account_name) -> None:
1271
+ def user_credentials_migrate_output(ctx: click.Context, account_name: str) -> None:
1269
1272
  accounts = queries.get_state_aws_accounts()
1270
1273
  state = init_state(integration="account-notifier")
1271
1274
  skip_accounts, appsre_pgp_key, _ = tfu.get_reencrypt_settings()
@@ -1307,7 +1310,7 @@ def user_credentials_migrate_output(ctx, account_name) -> None:
1307
1310
 
1308
1311
  @get.command()
1309
1312
  @click.pass_context
1310
- def aws_route53_zones(ctx):
1313
+ def aws_route53_zones(ctx: click.Context) -> None:
1311
1314
  zones = queries.get_dns_zones()
1312
1315
 
1313
1316
  results = []
@@ -1330,7 +1333,7 @@ def aws_route53_zones(ctx):
1330
1333
  @click.argument("cluster_name")
1331
1334
  @click.option("--cluster-admin/--no-cluster-admin", default=False)
1332
1335
  @click.pass_context
1333
- def bot_login(ctx, cluster_name, cluster_admin):
1336
+ def bot_login(ctx: click.Context, cluster_name: str, cluster_admin: bool) -> None:
1334
1337
  settings = queries.get_app_interface_settings()
1335
1338
  secret_reader = SecretReader(settings=settings)
1336
1339
  clusters = queries.get_clusters()
@@ -1353,7 +1356,7 @@ def bot_login(ctx, cluster_name, cluster_admin):
1353
1356
  )
1354
1357
  @click.argument("org_name")
1355
1358
  @click.pass_context
1356
- def ocm_login(ctx, org_name):
1359
+ def ocm_login(ctx: click.Context, org_name: str) -> None:
1357
1360
  settings = queries.get_app_interface_settings()
1358
1361
  secret_reader = SecretReader(settings=settings)
1359
1362
  ocms = [
@@ -1380,7 +1383,7 @@ def ocm_login(ctx, org_name):
1380
1383
  )
1381
1384
  @click.argument("account_name")
1382
1385
  @click.pass_context
1383
- def aws_creds(ctx, account_name):
1386
+ def aws_creds(ctx: click.Context, account_name: str) -> None:
1384
1387
  settings = queries.get_app_interface_settings()
1385
1388
  secret_reader = SecretReader(settings=settings)
1386
1389
  accounts = queries.get_aws_accounts(name=account_name)
@@ -1423,8 +1426,14 @@ def aws_creds(ctx, account_name):
1423
1426
  )
1424
1427
  @click.pass_context
1425
1428
  def copy_tfstate(
1426
- ctx, source_bucket, source_object_path, account_uid, rename, region, force
1427
- ):
1429
+ ctx: click.Context,
1430
+ source_bucket: str,
1431
+ source_object_path: str,
1432
+ account_uid: str,
1433
+ rename: str | None,
1434
+ region: str | None,
1435
+ force: bool,
1436
+ ) -> None:
1428
1437
  settings = queries.get_app_interface_settings()
1429
1438
  secret_reader = SecretReader(settings=settings)
1430
1439
  accounts = queries.get_aws_accounts(uid=account_uid, terraform_state=True)
@@ -1445,7 +1454,6 @@ def copy_tfstate(
1445
1454
  )
1446
1455
  return
1447
1456
 
1448
- dest_filename = ""
1449
1457
  if rename:
1450
1458
  dest_filename = rename.removesuffix(".tfstate")
1451
1459
  else:
@@ -1506,20 +1514,26 @@ def copy_tfstate(
1506
1514
  @get.command(short_help='obtain "rosa create cluster" command by cluster name')
1507
1515
  @click.argument("cluster_name")
1508
1516
  @click.pass_context
1509
- def rosa_create_cluster_command(ctx, cluster_name):
1517
+ def rosa_create_cluster_command(ctx: click.Context, cluster_name: str) -> None:
1510
1518
  clusters = [c for c in get_clusters() if c.name == cluster_name]
1511
- try:
1512
- cluster = clusters[0]
1513
- except IndexError:
1519
+ if not clusters:
1514
1520
  print(f"{cluster_name} not found.")
1515
1521
  sys.exit(1)
1522
+ cluster = clusters[0]
1516
1523
 
1517
- if cluster.spec.product != OCM_PRODUCT_ROSA:
1524
+ if (
1525
+ not cluster.spec
1526
+ or cluster.spec.product != OCM_PRODUCT_ROSA
1527
+ or not isinstance(cluster.spec, ClusterSpecROSAV1)
1528
+ ):
1518
1529
  print("must be a rosa cluster.")
1519
1530
  sys.exit(1)
1520
1531
 
1521
1532
  settings = queries.get_app_interface_settings()
1522
1533
  account = cluster.spec.account
1534
+ if not account:
1535
+ print("account not found.")
1536
+ sys.exit(1)
1523
1537
 
1524
1538
  if account.billing_account:
1525
1539
  billing_account = account.billing_account.uid
@@ -1529,6 +1543,19 @@ def rosa_create_cluster_command(ctx, cluster_name):
1529
1543
  ) as aws_api:
1530
1544
  billing_account = aws_api.get_organization_billing_account(account.name)
1531
1545
 
1546
+ if not cluster.spec.oidc_endpoint_url:
1547
+ print("oidc_endpoint_url not set.")
1548
+ sys.exit(1)
1549
+ if not cluster.spec.subnet_ids:
1550
+ print("subnet_ids not set.")
1551
+ sys.exit(1)
1552
+ if not cluster.network:
1553
+ print("network not set.")
1554
+ sys.exit(1)
1555
+ if not cluster.machine_pools:
1556
+ print("machine_pools not set.")
1557
+ sys.exit(1)
1558
+
1532
1559
  print(
1533
1560
  " ".join([
1534
1561
  "rosa create cluster",
@@ -1582,7 +1609,9 @@ def rosa_create_cluster_command(ctx, cluster_name):
1582
1609
  @click.argument("jumphost_hostname", required=False)
1583
1610
  @click.argument("cluster_name", required=False)
1584
1611
  @click.pass_context
1585
- def sshuttle_command(ctx, jumphost_hostname: str | None, cluster_name: str | None):
1612
+ def sshuttle_command(
1613
+ ctx: click.Context, jumphost_hostname: str | None, cluster_name: str | None
1614
+ ) -> None:
1586
1615
  jumphosts_query_data = queries.get_jumphosts(hostname=jumphost_hostname)
1587
1616
  jumphosts = jumphosts_query_data.jumphosts or []
1588
1617
  for jh in jumphosts:
@@ -1604,7 +1633,9 @@ def sshuttle_command(ctx, jumphost_hostname: str | None, cluster_name: str | Non
1604
1633
  @click.argument("instance_name")
1605
1634
  @click.argument("job_name")
1606
1635
  @click.pass_context
1607
- def jenkins_job_vault_secrets(ctx, instance_name: str, job_name: str) -> None:
1636
+ def jenkins_job_vault_secrets(
1637
+ ctx: click.Context, instance_name: str, job_name: str
1638
+ ) -> None:
1608
1639
  secret_reader = SecretReader(queries.get_secret_reader_settings())
1609
1640
  jjb: JJB = init_jjb(secret_reader, instance_name, config_name=None, print_only=True)
1610
1641
  jobs = jjb.get_all_jobs([job_name], instance_name)[instance_name]
@@ -1629,7 +1660,7 @@ def jenkins_job_vault_secrets(ctx, instance_name: str, job_name: str) -> None:
1629
1660
  @get.command()
1630
1661
  @click.argument("name", default="")
1631
1662
  @click.pass_context
1632
- def namespaces(ctx, name):
1663
+ def namespaces(ctx: click.Context, name: str) -> None:
1633
1664
  namespaces = queries.get_namespaces()
1634
1665
  if name:
1635
1666
  namespaces = [ns for ns in namespaces if ns["name"] == name]
@@ -1641,7 +1672,7 @@ def namespaces(ctx, name):
1641
1672
  print_output(ctx.obj["options"], namespaces, columns)
1642
1673
 
1643
1674
 
1644
- def add_resource(item, resource, columns):
1675
+ def add_resource(item: dict, resource: Mapping, columns: list[str]) -> None:
1645
1676
  provider = resource["provider"]
1646
1677
  if provider not in columns:
1647
1678
  columns.append(provider)
@@ -1652,11 +1683,11 @@ def add_resource(item, resource, columns):
1652
1683
 
1653
1684
  @get.command
1654
1685
  @click.pass_context
1655
- def cluster_openshift_resources(ctx):
1686
+ def cluster_openshift_resources(ctx: click.Context) -> None:
1656
1687
  gqlapi = gql.get_api()
1657
1688
  namespaces = gqlapi.query(orb.NAMESPACES_QUERY)["namespaces"]
1658
1689
  columns = ["name", "total"]
1659
- results = {}
1690
+ results: dict = {}
1660
1691
  for ns_info in namespaces:
1661
1692
  cluster_name = ns_info["cluster"]["name"]
1662
1693
  item = {"name": cluster_name, "total": 0}
@@ -1677,10 +1708,10 @@ def cluster_openshift_resources(ctx):
1677
1708
 
1678
1709
  @get.command
1679
1710
  @click.pass_context
1680
- def aws_terraform_resources(ctx):
1711
+ def aws_terraform_resources(ctx: click.Context) -> None:
1681
1712
  namespaces = tfr.get_namespaces()
1682
1713
  columns = ["name", "total"]
1683
- results = {}
1714
+ results: dict = {}
1684
1715
  for ns_info in namespaces:
1685
1716
  specs = (
1686
1717
  get_external_resource_specs(
@@ -1732,7 +1763,7 @@ def rds_region(
1732
1763
 
1733
1764
  @get.command
1734
1765
  @click.pass_context
1735
- def rds(ctx):
1766
+ def rds(ctx: click.Context) -> None:
1736
1767
  namespaces = tfr.get_namespaces()
1737
1768
  accounts = {a["name"]: a for a in queries.get_aws_accounts()}
1738
1769
  results = []
@@ -1810,7 +1841,7 @@ You can view the source of this Markdown to extract the JSON data.
1810
1841
 
1811
1842
  @get.command
1812
1843
  @click.pass_context
1813
- def rds_recommendations(ctx):
1844
+ def rds_recommendations(ctx: click.Context) -> None:
1814
1845
  IGNORED_STATUSES = ("resolved",)
1815
1846
  IGNORED_SEVERITIES = ("informational",)
1816
1847
 
@@ -1889,7 +1920,7 @@ def rds_recommendations(ctx):
1889
1920
 
1890
1921
  @get.command()
1891
1922
  @click.pass_context
1892
- def products(ctx):
1923
+ def products(ctx: click.Context) -> None:
1893
1924
  products = queries.get_products()
1894
1925
  columns = ["name", "description"]
1895
1926
  print_output(ctx.obj["options"], products, columns)
@@ -1898,7 +1929,7 @@ def products(ctx):
1898
1929
  @describe.command()
1899
1930
  @click.argument("name")
1900
1931
  @click.pass_context
1901
- def product(ctx, name):
1932
+ def product(ctx: click.Context, name: str) -> None:
1902
1933
  products = queries.get_products()
1903
1934
  products = [p for p in products if p["name"].lower() == name.lower()]
1904
1935
  if len(products) != 1:
@@ -1913,7 +1944,7 @@ def product(ctx, name):
1913
1944
 
1914
1945
  @get.command()
1915
1946
  @click.pass_context
1916
- def environments(ctx):
1947
+ def environments(ctx: click.Context) -> None:
1917
1948
  environments = queries.get_environments()
1918
1949
  columns = ["name", "description", "product.name"]
1919
1950
  # TODO(mafriedm): fix this
@@ -1925,7 +1956,7 @@ def environments(ctx):
1925
1956
  @describe.command()
1926
1957
  @click.argument("name")
1927
1958
  @click.pass_context
1928
- def environment(ctx, name):
1959
+ def environment(ctx: click.Context, name: str) -> None:
1929
1960
  environments = queries.get_environments()
1930
1961
  environments = [e for e in environments if e["name"].lower() == name.lower()]
1931
1962
  if len(environments) != 1:
@@ -1943,7 +1974,7 @@ def environment(ctx, name):
1943
1974
 
1944
1975
  @get.command()
1945
1976
  @click.pass_context
1946
- def services(ctx):
1977
+ def services(ctx: click.Context) -> None:
1947
1978
  apps = queries.get_apps()
1948
1979
  columns = ["name", "path", "onboardingStatus"]
1949
1980
  print_output(ctx.obj["options"], apps, columns)
@@ -1951,17 +1982,15 @@ def services(ctx):
1951
1982
 
1952
1983
  @get.command()
1953
1984
  @click.pass_context
1954
- def repos(ctx):
1985
+ def repos(ctx: click.Context) -> None:
1955
1986
  repos = queries.get_repos()
1956
- repos = [{"url": r} for r in repos]
1957
- columns = ["url"]
1958
- print_output(ctx.obj["options"], repos, columns)
1987
+ print_output(ctx.obj["options"], [{"url": r} for r in repos], ["url"])
1959
1988
 
1960
1989
 
1961
1990
  @get.command()
1962
1991
  @click.argument("org_username")
1963
1992
  @click.pass_context
1964
- def roles(ctx, org_username):
1993
+ def roles(ctx: click.Context, org_username: str) -> None:
1965
1994
  users = queries.get_roles()
1966
1995
  users = [u for u in users if u["org_username"] == org_username]
1967
1996
 
@@ -1972,7 +2001,7 @@ def roles(ctx, org_username):
1972
2001
  user = users[0]
1973
2002
 
1974
2003
  # type, name, resource, [ref]
1975
- roles: dict[(str, str, str), set] = defaultdict(set)
2004
+ roles: dict[tuple[str, str, str], set[str]] = defaultdict(set)
1976
2005
 
1977
2006
  for role in user["roles"]:
1978
2007
  role_name = role["path"]
@@ -2026,7 +2055,7 @@ def roles(ctx, org_username):
2026
2055
  @get.command()
2027
2056
  @click.argument("org_username", default="")
2028
2057
  @click.pass_context
2029
- def users(ctx, org_username):
2058
+ def users(ctx: click.Context, org_username: str) -> None:
2030
2059
  users = queries.get_users()
2031
2060
  if org_username:
2032
2061
  users = [u for u in users if u["org_username"] == org_username]
@@ -2037,7 +2066,7 @@ def users(ctx, org_username):
2037
2066
 
2038
2067
  @get.command()
2039
2068
  @click.pass_context
2040
- def integrations(ctx):
2069
+ def integrations(ctx: click.Context) -> None:
2041
2070
  environments = queries.get_integrations()
2042
2071
  columns = ["name", "description"]
2043
2072
  print_output(ctx.obj["options"], environments, columns)
@@ -2045,7 +2074,7 @@ def integrations(ctx):
2045
2074
 
2046
2075
  @get.command()
2047
2076
  @click.pass_context
2048
- def quay_mirrors(ctx):
2077
+ def quay_mirrors(ctx: click.Context) -> None:
2049
2078
  apps = queries.get_quay_repos()
2050
2079
 
2051
2080
  mirrors = []
@@ -2083,7 +2112,9 @@ def quay_mirrors(ctx):
2083
2112
  @click.argument("kind")
2084
2113
  @click.argument("name")
2085
2114
  @click.pass_context
2086
- def root_owner(ctx, cluster, namespace, kind, name):
2115
+ def root_owner(
2116
+ ctx: click.Context, cluster: str, namespace: str, kind: str, name: str
2117
+ ) -> None:
2087
2118
  settings = queries.get_app_interface_settings()
2088
2119
  clusters = [c for c in queries.get_clusters(minimal=True) if c["name"] == cluster]
2089
2120
  oc_map = OC_Map(
@@ -2113,7 +2144,9 @@ def root_owner(ctx, cluster, namespace, kind, name):
2113
2144
  @click.argument("aws_account")
2114
2145
  @click.argument("identifier")
2115
2146
  @click.pass_context
2116
- def service_owners_for_rds_instance(ctx, aws_account, identifier):
2147
+ def service_owners_for_rds_instance(
2148
+ ctx: click.Context, aws_account: str, identifier: str
2149
+ ) -> None:
2117
2150
  namespaces = queries.get_namespaces()
2118
2151
  service_owners = []
2119
2152
  for namespace_info in namespaces:
@@ -2135,7 +2168,7 @@ def service_owners_for_rds_instance(ctx, aws_account, identifier):
2135
2168
 
2136
2169
  @get.command()
2137
2170
  @click.pass_context
2138
- def sre_checkpoints(ctx):
2171
+ def sre_checkpoints(ctx: click.Context) -> None:
2139
2172
  apps = queries.get_apps()
2140
2173
 
2141
2174
  parent_apps = {app["parentApp"]["path"] for app in apps if app.get("parentApp")}
@@ -2159,13 +2192,14 @@ def sre_checkpoints(ctx):
2159
2192
 
2160
2193
  @get.command()
2161
2194
  @click.pass_context
2162
- def app_interface_merge_queue(ctx):
2195
+ def app_interface_merge_queue(ctx: click.Context) -> None:
2163
2196
  import reconcile.gitlab_housekeeping as glhk
2164
2197
 
2165
2198
  settings = queries.get_app_interface_settings()
2166
2199
  instance = queries.get_gitlab_instance()
2167
2200
  gl = GitLabApi(instance, project_url=settings["repoUrl"], settings=settings)
2168
- merge_requests = glhk.get_merge_requests(True, gl, state=None)
2201
+ state = init_state(integration=glhk.QONTRACT_INTEGRATION)
2202
+ merge_requests = glhk.get_merge_requests(True, gl, state=state)
2169
2203
 
2170
2204
  columns = [
2171
2205
  "id",
@@ -2200,7 +2234,7 @@ def app_interface_merge_queue(ctx):
2200
2234
 
2201
2235
  @get.command()
2202
2236
  @click.pass_context
2203
- def app_interface_review_queue(ctx) -> None:
2237
+ def app_interface_review_queue(ctx: click.Context) -> None:
2204
2238
  import reconcile.gitlab_housekeeping as glhk
2205
2239
 
2206
2240
  settings = queries.get_app_interface_settings()
@@ -2217,7 +2251,7 @@ def app_interface_review_queue(ctx) -> None:
2217
2251
  "labels",
2218
2252
  ]
2219
2253
 
2220
- def get_mrs(repo, url) -> list[dict[str, str]]:
2254
+ def get_mrs(repo: str, url: str) -> list[dict[str, str]]:
2221
2255
  gl = GitLabApi(instance, project_url=url, settings=settings)
2222
2256
  merge_requests = gl.get_merge_requests(state=MRState.OPENED)
2223
2257
  try:
@@ -2312,7 +2346,7 @@ def app_interface_review_queue(ctx) -> None:
2312
2346
 
2313
2347
  @get.command()
2314
2348
  @click.pass_context
2315
- def app_interface_open_selfserviceable_mr_queue(ctx):
2349
+ def app_interface_open_selfserviceable_mr_queue(ctx: click.Context) -> None:
2316
2350
  settings = queries.get_app_interface_settings()
2317
2351
  instance = queries.get_gitlab_instance()
2318
2352
  gl = GitLabApi(instance, project_url=settings["repoUrl"], settings=settings)
@@ -2375,7 +2409,7 @@ def app_interface_open_selfserviceable_mr_queue(ctx):
2375
2409
 
2376
2410
  @get.command()
2377
2411
  @click.pass_context
2378
- def change_types(ctx) -> None:
2412
+ def change_types(ctx: click.Context) -> None:
2379
2413
  """List all change types."""
2380
2414
  change_types = fetch_change_type_processors(gql.get_api(), NoOpFileDiffResolver())
2381
2415
 
@@ -2400,7 +2434,7 @@ def change_types(ctx) -> None:
2400
2434
 
2401
2435
  @get.command()
2402
2436
  @click.pass_context
2403
- def app_interface_merge_history(ctx):
2437
+ def app_interface_merge_history(ctx: click.Context) -> None:
2404
2438
  settings = queries.get_app_interface_settings()
2405
2439
  instance = queries.get_gitlab_instance()
2406
2440
  gl = GitLabApi(instance, project_url=settings["repoUrl"], settings=settings)
@@ -2437,7 +2471,7 @@ def app_interface_merge_history(ctx):
2437
2471
  )
2438
2472
  @use_jump_host()
2439
2473
  @click.pass_context
2440
- def selectorsyncset_managed_resources(ctx, use_jump_host):
2474
+ def selectorsyncset_managed_resources(ctx: click.Context, use_jump_host: bool) -> None:
2441
2475
  vault_settings = get_app_interface_vault_settings()
2442
2476
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
2443
2477
  clusters = get_clusters()
@@ -2495,7 +2529,9 @@ def selectorsyncset_managed_resources(ctx, use_jump_host):
2495
2529
  )
2496
2530
  @use_jump_host()
2497
2531
  @click.pass_context
2498
- def selectorsyncset_managed_hypershift_resources(ctx, use_jump_host):
2532
+ def selectorsyncset_managed_hypershift_resources(
2533
+ ctx: click.Context, use_jump_host: bool
2534
+ ) -> None:
2499
2535
  vault_settings = get_app_interface_vault_settings()
2500
2536
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
2501
2537
  clusters = get_clusters()
@@ -2573,7 +2609,12 @@ def selectorsyncset_managed_hypershift_resources(ctx, use_jump_host):
2573
2609
  default=os.environ.get("QONTRACT_CLI_EC2_JENKINS_WORKER_AWS_REGION", "us-east-1"),
2574
2610
  )
2575
2611
  @click.pass_context
2576
- def ec2_jenkins_workers(ctx, aws_access_key_id, aws_secret_access_key, aws_region):
2612
+ def ec2_jenkins_workers(
2613
+ ctx: click.Context,
2614
+ aws_access_key_id: str,
2615
+ aws_secret_access_key: str,
2616
+ aws_region: str,
2617
+ ) -> None:
2577
2618
  """Prints a list of jenkins workers and their status."""
2578
2619
  if not aws_access_key_id or not aws_secret_access_key:
2579
2620
  raise click.ClickException(
@@ -2620,9 +2661,9 @@ def ec2_jenkins_workers(ctx, aws_access_key_id, aws_secret_access_key, aws_regio
2620
2661
  url = ""
2621
2662
  for t in instance.tags:
2622
2663
  if t.get("Key") == "os":
2623
- os = t.get("Value")
2664
+ os = t["Value"]
2624
2665
  if t.get("Key") == "jenkins_controller":
2625
- url = f"https://{t.get('Value').replace('-', '.')}.devshift.net/computer/{instance.id}"
2666
+ url = f"https://{t['Value'].replace('-', '.')}.devshift.net/computer/{instance.id}"
2626
2667
  image = ec2.Image(instance.image_id)
2627
2668
  commit_url = ""
2628
2669
  for t in image.tags:
@@ -2649,7 +2690,7 @@ def ec2_jenkins_workers(ctx, aws_access_key_id, aws_secret_access_key, aws_regio
2649
2690
  @get.command()
2650
2691
  @click.argument("status-board-instance")
2651
2692
  @click.pass_context
2652
- def slo_document_services(ctx, status_board_instance):
2693
+ def slo_document_services(ctx: click.Context, status_board_instance: str) -> None:
2653
2694
  """Print SLO Documents Services"""
2654
2695
  columns = [
2655
2696
  "slo_doc_name",
@@ -2678,7 +2719,7 @@ def slo_document_services(ctx, status_board_instance):
2678
2719
  slodocs = []
2679
2720
  for slodoc in get_slo_documents():
2680
2721
  products = [ns.namespace.environment.product.name for ns in slodoc.namespaces]
2681
- for slo in slodoc.slos:
2722
+ for slo in slodoc.slos or []:
2682
2723
  for product in products:
2683
2724
  if slodoc.app.parent_app:
2684
2725
  app = f"{slodoc.app.parent_app.name}-{slodoc.app.name}"
@@ -2704,7 +2745,7 @@ def slo_document_services(ctx, status_board_instance):
2704
2745
  "target_unit": slo.slo_target_unit,
2705
2746
  "window": slo.slo_parameters.window,
2706
2747
  "statusBoardService": f"{product}/{slodoc.app.name}/{slo.name}",
2707
- "statusBoardEnabled": "statusBoard" in slodoc.labels,
2748
+ "statusBoardEnabled": "statusBoard" in (slodoc.labels or {}),
2708
2749
  }
2709
2750
  slodocs.append(item)
2710
2751
 
@@ -2714,7 +2755,7 @@ def slo_document_services(ctx, status_board_instance):
2714
2755
  @get.command()
2715
2756
  @click.argument("file_path")
2716
2757
  @click.pass_context
2717
- def alerts(ctx, file_path):
2758
+ def alerts(ctx: click.Context, file_path: str) -> None:
2718
2759
  BIG_NUMBER = 10
2719
2760
 
2720
2761
  def sort_by_threshold(item: dict[str, str]) -> int:
@@ -2788,7 +2829,7 @@ def alerts(ctx, file_path):
2788
2829
  @get.command()
2789
2830
  @click.pass_context
2790
2831
  @thread_pool_size(default=5)
2791
- def aws_cost_report(ctx, thread_pool_size):
2832
+ def aws_cost_report(ctx: click.Context, thread_pool_size: int) -> None:
2792
2833
  command = AwsCostReportCommand.create(thread_pool_size=thread_pool_size)
2793
2834
  print(command.execute())
2794
2835
 
@@ -2796,7 +2837,7 @@ def aws_cost_report(ctx, thread_pool_size):
2796
2837
  @get.command()
2797
2838
  @click.pass_context
2798
2839
  @thread_pool_size(default=5)
2799
- def openshift_cost_report(ctx, thread_pool_size):
2840
+ def openshift_cost_report(ctx: click.Context, thread_pool_size: int) -> None:
2800
2841
  command = OpenShiftCostReportCommand.create(thread_pool_size=thread_pool_size)
2801
2842
  print(command.execute())
2802
2843
 
@@ -2804,7 +2845,9 @@ def openshift_cost_report(ctx, thread_pool_size):
2804
2845
  @get.command()
2805
2846
  @click.pass_context
2806
2847
  @thread_pool_size(default=5)
2807
- def openshift_cost_optimization_report(ctx, thread_pool_size):
2848
+ def openshift_cost_optimization_report(
2849
+ ctx: click.Context, thread_pool_size: int
2850
+ ) -> None:
2808
2851
  command = OpenShiftCostOptimizationReportCommand.create(
2809
2852
  thread_pool_size=thread_pool_size
2810
2853
  )
@@ -2813,7 +2856,7 @@ def openshift_cost_optimization_report(ctx, thread_pool_size):
2813
2856
 
2814
2857
  @get.command()
2815
2858
  @click.pass_context
2816
- def osd_component_versions(ctx):
2859
+ def osd_component_versions(ctx: click.Context) -> None:
2817
2860
  osd_environments = [
2818
2861
  e["name"] for e in queries.get_environments() if e["product"]["name"] == "OSDv4"
2819
2862
  ]
@@ -2849,7 +2892,7 @@ def osd_component_versions(ctx):
2849
2892
 
2850
2893
  @get.command()
2851
2894
  @click.pass_context
2852
- def maintenances(ctx):
2895
+ def maintenances(ctx: click.Context) -> None:
2853
2896
  now = datetime.now(UTC)
2854
2897
  maintenances = maintenances_gql.query(gql.get_api().query).maintenances or []
2855
2898
  data = [
@@ -2912,7 +2955,7 @@ class MigrationStatusCount:
2912
2955
 
2913
2956
  @get.command()
2914
2957
  @click.pass_context
2915
- def hcp_migration_status(ctx):
2958
+ def hcp_migration_status(ctx: click.Context) -> None:
2916
2959
  counts: dict[str, MigrationStatusCount] = {}
2917
2960
  total_count = MigrationStatusCount("total")
2918
2961
  saas_files = get_saas_files()
@@ -2951,7 +2994,7 @@ def hcp_migration_status(ctx):
2951
2994
 
2952
2995
  @get.command()
2953
2996
  @click.pass_context
2954
- def systems_and_tools(ctx):
2997
+ def systems_and_tools(ctx: click.Context) -> None:
2955
2998
  print(
2956
2999
  f"This report is obtained from app-interface Graphql endpoint available at: {config.get_config()['graphql']['server']}"
2957
3000
  )
@@ -2965,7 +3008,7 @@ def systems_and_tools(ctx):
2965
3008
  "--environment_name", default="production", help="environment to get logs from"
2966
3009
  )
2967
3010
  @click.pass_context
2968
- def logs(ctx, integration_name: str, environment_name: str):
3011
+ def logs(ctx: click.Context, integration_name: str, environment_name: str) -> None:
2969
3012
  integrations = [
2970
3013
  i
2971
3014
  for i in integrations_gql.query(query_func=gql.get_api().query).integrations
@@ -3004,7 +3047,7 @@ def logs(ctx, integration_name: str, environment_name: str):
3004
3047
 
3005
3048
  @get.command
3006
3049
  @click.pass_context
3007
- def jenkins_jobs(ctx):
3050
+ def jenkins_jobs(ctx: click.Context) -> None:
3008
3051
  jenkins_configs = queries.get_jenkins_configs()
3009
3052
 
3010
3053
  # stats dicts
@@ -3074,9 +3117,9 @@ You can view the source of this Markdown to extract the JSON data.
3074
3117
 
3075
3118
  @get.command
3076
3119
  @click.pass_context
3077
- def container_image_details(ctx):
3120
+ def container_image_details(ctx: click.Context) -> None:
3078
3121
  apps = get_apps_quay_repos_escalation_policies()
3079
- data: list[dict[str, str]] = []
3122
+ data: list[dict[str, str | list[str]]] = []
3080
3123
  for app in apps:
3081
3124
  app_name = f"{app.parent_app.name}/{app.name}" if app.parent_app else app.name
3082
3125
  ep_channels = app.escalation_policy.channels
@@ -3088,7 +3131,7 @@ def container_image_details(ctx):
3088
3131
  if repo.mirror:
3089
3132
  continue
3090
3133
  repository = f"quay.io/{org_name}/{repo.name}"
3091
- item = {
3134
+ item: dict[str, str | list[str]] = {
3092
3135
  "app": app_name,
3093
3136
  "repository": repository,
3094
3137
  "email": email,
@@ -3101,27 +3144,25 @@ def container_image_details(ctx):
3101
3144
 
3102
3145
  @get.command
3103
3146
  @click.pass_context
3104
- def change_log_tracking(ctx):
3147
+ def change_log_tracking(ctx: click.Context) -> None:
3105
3148
  repo_url = get_app_interface_repo_url()
3106
3149
  change_types = fetch_change_type_processors(gql.get_api(), NoOpFileDiffResolver())
3107
3150
  state = init_state(integration=cl.QONTRACT_INTEGRATION)
3108
3151
  change_log = ChangeLog(**state.get(BUNDLE_DIFFS_OBJ))
3109
3152
  data: list[dict[str, str]] = []
3110
- for item in change_log.items:
3111
- change_log_item = ChangeLogItem(**item)
3153
+ for change_log_item in change_log.items:
3112
3154
  commit = change_log_item.commit
3113
3155
  covered_change_types_descriptions = [
3114
3156
  ct.description
3115
3157
  for ct in change_types
3116
3158
  if ct.name in change_log_item.change_types
3117
3159
  ]
3118
- item = {
3160
+ data.append({
3119
3161
  "commit": f"[{commit[:7]}]({repo_url}/commit/{commit})",
3120
3162
  "merged_at": change_log_item.merged_at,
3121
3163
  "apps": ", ".join(change_log_item.apps),
3122
3164
  "changes": ", ".join(covered_change_types_descriptions),
3123
- }
3124
- data.append(item)
3165
+ })
3125
3166
 
3126
3167
  # TODO(mafriedm): Fix this
3127
3168
  ctx.obj["options"]["sort"] = False
@@ -3132,7 +3173,7 @@ def change_log_tracking(ctx):
3132
3173
  @root.group(name="set")
3133
3174
  @output
3134
3175
  @click.pass_context
3135
- def set_command(ctx, output):
3176
+ def set_command(ctx: click.Context, output: str) -> None:
3136
3177
  ctx.obj["output"] = output
3137
3178
 
3138
3179
 
@@ -3141,7 +3182,9 @@ def set_command(ctx, output):
3141
3182
  @click.argument("usergroup")
3142
3183
  @click.argument("username")
3143
3184
  @click.pass_context
3144
- def slack_usergroup(ctx, workspace, usergroup, username):
3185
+ def slack_usergroup(
3186
+ ctx: click.Context, workspace: str, usergroup: str, username: str
3187
+ ) -> None:
3145
3188
  """Update users in a slack usergroup.
3146
3189
  Use an org_username as the username.
3147
3190
  To empty a slack usergroup, pass '' (empty string) as the username.
@@ -3149,6 +3192,8 @@ def slack_usergroup(ctx, workspace, usergroup, username):
3149
3192
  settings = queries.get_app_interface_settings()
3150
3193
  slack = slackapi_from_queries("qontract-cli")
3151
3194
  ugid = slack.get_usergroup_id(usergroup)
3195
+ if not ugid:
3196
+ raise click.ClickException(f"Usergroup {usergroup} not found.")
3152
3197
  if username:
3153
3198
  mail_address = settings["smtp"]["mailAddress"]
3154
3199
  users = [slack.get_user_id_by_name(username, mail_address)]
@@ -3157,33 +3202,17 @@ def slack_usergroup(ctx, workspace, usergroup, username):
3157
3202
  slack.update_usergroup_users(ugid, users)
3158
3203
 
3159
3204
 
3160
- @set_command.command()
3161
- @click.argument("org_name")
3162
- @click.argument("cluster_name")
3163
- @click.pass_context
3164
- def cluster_admin(ctx, org_name, cluster_name):
3165
- settings = queries.get_app_interface_settings()
3166
- ocms = [
3167
- o for o in queries.get_openshift_cluster_managers() if o["name"] == org_name
3168
- ]
3169
- ocm_map = OCMMap(ocms=ocms, settings=settings)
3170
- ocm = ocm_map[org_name]
3171
- enabled = ocm.is_cluster_admin_enabled(cluster_name)
3172
- if not enabled:
3173
- ocm.enable_cluster_admin(cluster_name)
3174
-
3175
-
3176
3205
  @root.group()
3177
3206
  @environ(["APP_INTERFACE_STATE_BUCKET"])
3178
3207
  @click.pass_context
3179
- def state(ctx):
3208
+ def state(ctx: click.Context) -> None:
3180
3209
  pass
3181
3210
 
3182
3211
 
3183
3212
  @state.command()
3184
3213
  @click.argument("integration", default="")
3185
3214
  @click.pass_context
3186
- def ls(ctx, integration):
3215
+ def ls(ctx: click.Context, integration: str) -> None:
3187
3216
  state = init_state(integration=integration)
3188
3217
  keys = state.ls()
3189
3218
  # if integration in not defined the 2th token will be the integration name
@@ -3204,7 +3233,7 @@ def ls(ctx, integration):
3204
3233
  @click.argument("integration")
3205
3234
  @click.argument("key")
3206
3235
  @click.pass_context
3207
- def state_get(ctx, integration, key):
3236
+ def state_get(ctx: click.Context, integration: str, key: str) -> None:
3208
3237
  state = init_state(integration=integration)
3209
3238
  value = state.get(key)
3210
3239
  print(value)
@@ -3214,7 +3243,7 @@ def state_get(ctx, integration, key):
3214
3243
  @click.argument("integration")
3215
3244
  @click.argument("key")
3216
3245
  @click.pass_context
3217
- def add(ctx, integration, key):
3246
+ def add(ctx: click.Context, integration: str, key: str) -> None:
3218
3247
  state = init_state(integration=integration)
3219
3248
  state.add(key)
3220
3249
 
@@ -3224,7 +3253,7 @@ def add(ctx, integration, key):
3224
3253
  @click.argument("key")
3225
3254
  @click.argument("value")
3226
3255
  @click.pass_context
3227
- def state_set(ctx, integration, key, value):
3256
+ def state_set(ctx: click.Context, integration: str, key: str, value: str) -> None:
3228
3257
  state = init_state(integration=integration)
3229
3258
  state.add(key, value=value, force=True)
3230
3259
 
@@ -3233,7 +3262,7 @@ def state_set(ctx, integration, key, value):
3233
3262
  @click.argument("integration")
3234
3263
  @click.argument("key")
3235
3264
  @click.pass_context
3236
- def rm(ctx, integration, key):
3265
+ def rm(ctx: click.Context, integration: str, key: str) -> None:
3237
3266
  state = init_state(integration=integration)
3238
3267
  state.rm(key)
3239
3268
 
@@ -3241,7 +3270,7 @@ def rm(ctx, integration, key):
3241
3270
  @root.group()
3242
3271
  @environ(["APP_INTERFACE_STATE_BUCKET"])
3243
3272
  @click.pass_context
3244
- def early_exit_cache(ctx):
3273
+ def early_exit_cache(ctx: click.Context) -> None:
3245
3274
  pass
3246
3275
 
3247
3276
 
@@ -3277,13 +3306,13 @@ def early_exit_cache(ctx):
3277
3306
  )
3278
3307
  @click.pass_context
3279
3308
  def early_exit_cache_head(
3280
- ctx,
3281
- integration,
3282
- integration_version,
3283
- dry_run,
3284
- cache_source,
3285
- shard,
3286
- ):
3309
+ ctx: click.Context,
3310
+ integration: str,
3311
+ integration_version: str,
3312
+ dry_run: bool,
3313
+ cache_source: str,
3314
+ shard: str,
3315
+ ) -> None:
3287
3316
  with EarlyExitCache.build() as cache:
3288
3317
  cache_key = CacheKey(
3289
3318
  integration=integration,
@@ -3329,13 +3358,13 @@ def early_exit_cache_head(
3329
3358
  )
3330
3359
  @click.pass_context
3331
3360
  def early_exit_cache_get(
3332
- ctx,
3333
- integration,
3334
- integration_version,
3335
- dry_run,
3336
- cache_source,
3337
- shard,
3338
- ):
3361
+ ctx: click.Context,
3362
+ integration: str,
3363
+ integration_version: str,
3364
+ dry_run: bool,
3365
+ cache_source: str,
3366
+ shard: str,
3367
+ ) -> None:
3339
3368
  with EarlyExitCache.build() as cache:
3340
3369
  cache_key = CacheKey(
3341
3370
  integration=integration,
@@ -3412,18 +3441,18 @@ def early_exit_cache_get(
3412
3441
  )
3413
3442
  @click.pass_context
3414
3443
  def early_exit_cache_set(
3415
- ctx,
3416
- integration,
3417
- integration_version,
3418
- dry_run,
3419
- cache_source,
3420
- shard,
3421
- payload,
3422
- log_output,
3423
- applied_count,
3424
- ttl,
3425
- latest_cache_source_digest,
3426
- ):
3444
+ ctx: click.Context,
3445
+ integration: str,
3446
+ integration_version: str,
3447
+ dry_run: bool,
3448
+ cache_source: str,
3449
+ shard: str,
3450
+ payload: str,
3451
+ log_output: str,
3452
+ applied_count: int,
3453
+ ttl: int,
3454
+ latest_cache_source_digest: str,
3455
+ ) -> None:
3427
3456
  with EarlyExitCache.build() as cache:
3428
3457
  cache_key = CacheKey(
3429
3458
  integration=integration,
@@ -3472,13 +3501,13 @@ def early_exit_cache_set(
3472
3501
  )
3473
3502
  @click.pass_context
3474
3503
  def early_exit_cache_delete(
3475
- ctx,
3476
- integration,
3477
- integration_version,
3478
- dry_run,
3479
- cache_source_digest,
3480
- shard,
3481
- ):
3504
+ ctx: click.Context,
3505
+ integration: str,
3506
+ integration_version: str,
3507
+ dry_run: bool,
3508
+ cache_source_digest: str,
3509
+ shard: str,
3510
+ ) -> None:
3482
3511
  with EarlyExitCache.build() as cache:
3483
3512
  cache_key_with_digest = CacheKeyWithDigest(
3484
3513
  integration=integration,
@@ -3509,25 +3538,33 @@ def early_exit_cache_delete(
3509
3538
  type=click.Choice(["config", "vault"]),
3510
3539
  )
3511
3540
  @click.pass_context
3512
- def template(ctx, cluster, namespace, kind, name, path, secret_reader):
3541
+ def template(
3542
+ ctx: click.Context,
3543
+ cluster: str,
3544
+ namespace: str,
3545
+ kind: str,
3546
+ name: str,
3547
+ path: str,
3548
+ secret_reader: str,
3549
+ ) -> None:
3513
3550
  gqlapi = gql.get_api()
3514
3551
  namespaces = gqlapi.query(orb.NAMESPACES_QUERY)["namespaces"]
3515
- namespace_info = [
3552
+ namespaces_info = [
3516
3553
  n
3517
3554
  for n in namespaces
3518
3555
  if n["cluster"]["name"] == cluster and n["name"] == namespace
3519
3556
  ]
3520
- if len(namespace_info) != 1:
3557
+ if len(namespaces_info) != 1:
3521
3558
  print(f"{cluster}/{namespace} error")
3522
3559
  sys.exit(1)
3523
3560
 
3561
+ namespace_info = namespaces_info[0]
3524
3562
  settings = queries.get_app_interface_settings()
3525
3563
  settings["vault"] = secret_reader == "vault"
3526
3564
 
3527
3565
  if path and path.startswith("resources"):
3528
3566
  path = path.replace("resources", "", 1)
3529
3567
 
3530
- [namespace_info] = namespace_info
3531
3568
  ob.aggregate_shared_resources(namespace_info, "openshiftResources")
3532
3569
  openshift_resources = namespace_info.get("openshiftResources")
3533
3570
  for r in openshift_resources:
@@ -3568,7 +3605,9 @@ def template(ctx, cluster, namespace, kind, name, path, secret_reader):
3568
3605
  type=click.Choice(["config", "vault"]),
3569
3606
  )
3570
3607
  @click.pass_context
3571
- def run_prometheus_test(ctx, path, cluster, namespace, secret_reader):
3608
+ def run_prometheus_test(
3609
+ ctx: click.Context, path: str, cluster: str, namespace: str, secret_reader: str
3610
+ ) -> None:
3572
3611
  """Run prometheus tests for the rule associated with the test in the PATH from given
3573
3612
  CLUSTER/NAMESPACE"""
3574
3613
 
@@ -3654,17 +3693,17 @@ def run_prometheus_test(ctx, path, cluster, namespace, secret_reader):
3654
3693
  )
3655
3694
  @click.pass_context
3656
3695
  def alert_to_receiver(
3657
- ctx,
3658
- cluster,
3659
- namespace,
3660
- rules_path,
3661
- alert_name,
3662
- alertmanager_secret_path,
3663
- alertmanager_namespace,
3664
- alertmanager_secret_key,
3665
- secret_reader,
3666
- additional_label,
3667
- ):
3696
+ ctx: click.Context,
3697
+ cluster: str,
3698
+ namespace: str,
3699
+ rules_path: str,
3700
+ alert_name: str,
3701
+ alertmanager_secret_path: str,
3702
+ alertmanager_namespace: str,
3703
+ alertmanager_secret_key: str,
3704
+ secret_reader: str,
3705
+ additional_label: list[str],
3706
+ ) -> None:
3668
3707
  additional_labels = {}
3669
3708
  for al in additional_label:
3670
3709
  try:
@@ -3756,12 +3795,12 @@ def alert_to_receiver(
3756
3795
  print(f"Cannot find alert {alert_name} in rules {rules_path}")
3757
3796
  sys.exit(1)
3758
3797
 
3759
- for al in alert_labels:
3760
- result = amtool.config_routes_test(am_config, al)
3798
+ for label in alert_labels:
3799
+ result = amtool.config_routes_test(am_config, label)
3761
3800
  if not result:
3762
3801
  print(f"Error running amtool: {result}")
3763
3802
  sys.exit(1)
3764
- print("|".join([al["alertname"], str(result)]))
3803
+ print("|".join([label["alertname"], str(result)]))
3765
3804
 
3766
3805
 
3767
3806
  @root.command()
@@ -3769,7 +3808,12 @@ def alert_to_receiver(
3769
3808
  @click.option("--saas-file-name", default=None, help="saas-file to act on.")
3770
3809
  @click.option("--env-name", default=None, help="environment to use for parameters.")
3771
3810
  @click.pass_context
3772
- def saas_dev(ctx, app_name=None, saas_file_name=None, env_name=None) -> None:
3811
+ def saas_dev(
3812
+ ctx: click.Context,
3813
+ app_name: str | None = None,
3814
+ saas_file_name: str | None = None,
3815
+ env_name: str | None = None,
3816
+ ) -> None:
3773
3817
  if not env_name:
3774
3818
  print("env-name must be defined")
3775
3819
  return
@@ -3817,7 +3861,7 @@ def saas_dev(ctx, app_name=None, saas_file_name=None, env_name=None) -> None:
3817
3861
  @click.option("--app-name", default=None, help="app to act on.")
3818
3862
  @click.pass_context
3819
3863
  def saas_targets(
3820
- ctx, saas_file_name: str | None = None, app_name: str | None = None
3864
+ ctx: click.Context, saas_file_name: str | None = None, app_name: str | None = None
3821
3865
  ) -> None:
3822
3866
  """Resolve namespaceSelectors and print all resulting targets of a saas file."""
3823
3867
  console = Console()
@@ -3881,7 +3925,7 @@ def saas_targets(
3881
3925
  default="json",
3882
3926
  type=click.Choice(["json", "yaml"]),
3883
3927
  )
3884
- def query(output, query):
3928
+ def query(output: str, query: str) -> None:
3885
3929
  """Run a raw GraphQL query"""
3886
3930
  gqlapi = gql.get_api()
3887
3931
  result = gqlapi.query(query)
@@ -3895,7 +3939,7 @@ def query(output, query):
3895
3939
  @root.command()
3896
3940
  @click.argument("cluster")
3897
3941
  @click.argument("query")
3898
- def promquery(cluster, query):
3942
+ def promquery(cluster: str, query: str) -> None:
3899
3943
  """Run a PromQL query"""
3900
3944
  config_data = config.get_config()
3901
3945
  auth = {"path": config_data["promql-auth"]["secret_path"], "field": "token"}
@@ -3946,8 +3990,13 @@ def promquery(cluster, query):
3946
3990
  default=False,
3947
3991
  )
3948
3992
  def sre_checkpoint_metadata(
3949
- app_path, parent_ticket, jiraboard, jiradef, create_parent_ticket, dry_run
3950
- ):
3993
+ app_path: str,
3994
+ parent_ticket: str,
3995
+ jiraboard: str,
3996
+ jiradef: str,
3997
+ create_parent_ticket: bool,
3998
+ dry_run: bool,
3999
+ ) -> None:
3951
4000
  """Check an app path for checkpoint-related metadata."""
3952
4001
  data = queries.get_app_metadata(app_path)
3953
4002
  settings = queries.get_app_interface_settings()
@@ -3986,8 +4035,13 @@ def sre_checkpoint_metadata(
3986
4035
  required=True,
3987
4036
  )
3988
4037
  def gpg_encrypt(
3989
- vault_path, vault_secret_version, file_path, openshift_path, output, for_user
3990
- ):
4038
+ vault_path: str,
4039
+ vault_secret_version: str,
4040
+ file_path: str,
4041
+ openshift_path: str,
4042
+ output: str,
4043
+ for_user: str,
4044
+ ) -> None:
3991
4045
  """
3992
4046
  Encrypt the specified secret (local file, vault or openshift) with a
3993
4047
  given users gpg key. This is intended for easily sharing secrets with
@@ -4010,7 +4064,7 @@ def gpg_encrypt(
4010
4064
  @click.option("--channel", help="the channel that state is part of")
4011
4065
  @click.option("--sha", help="the commit sha we want state for")
4012
4066
  @environ(["APP_INTERFACE_STATE_BUCKET"])
4013
- def get_promotion_state(channel: str, sha: str):
4067
+ def get_promotion_state(channel: str, sha: str) -> None:
4014
4068
  from tools.saas_promotion_state.saas_promotion_state import (
4015
4069
  SaasPromotionState,
4016
4070
  )
@@ -4035,7 +4089,7 @@ def get_promotion_state(channel: str, sha: str):
4035
4089
  @click.option("--sha", help="the commit sha we want state for")
4036
4090
  @click.option("--publisher-id", help="the publisher id we want state for")
4037
4091
  @environ(["APP_INTERFACE_STATE_BUCKET"])
4038
- def mark_promotion_state_successful(channel: str, sha: str, publisher_id: str):
4092
+ def mark_promotion_state_successful(channel: str, sha: str, publisher_id: str) -> None:
4039
4093
  from tools.saas_promotion_state.saas_promotion_state import (
4040
4094
  SaasPromotionState,
4041
4095
  )
@@ -4059,7 +4113,9 @@ def mark_promotion_state_successful(channel: str, sha: str, publisher_id: str):
4059
4113
  help="filesystem path to a local app-interface repo",
4060
4114
  default=os.environ.get("APP_INTERFACE_PATH", None),
4061
4115
  )
4062
- def test_change_type(change_type_name: str, role_name: str, app_interface_path: str):
4116
+ def test_change_type(
4117
+ change_type_name: str, role_name: str, app_interface_path: str
4118
+ ) -> None:
4063
4119
  from reconcile.change_owners import tester
4064
4120
 
4065
4121
  # tester.test_change_type(change_type_name, datafile_path)
@@ -4068,7 +4124,7 @@ def test_change_type(change_type_name: str, role_name: str, app_interface_path:
4068
4124
 
4069
4125
  @root.group()
4070
4126
  @click.pass_context
4071
- def sso_client(ctx):
4127
+ def sso_client(ctx: click.Context) -> None:
4072
4128
  """SSO client commands"""
4073
4129
 
4074
4130
 
@@ -4104,7 +4160,7 @@ def sso_client(ctx):
4104
4160
  )
4105
4161
  @click.pass_context
4106
4162
  def create(
4107
- ctx,
4163
+ ctx: click.Context,
4108
4164
  client_name: str,
4109
4165
  contact_email: str,
4110
4166
  keycloak_instance_vault_path: str,
@@ -4138,7 +4194,7 @@ def create(
4138
4194
  @sso_client.command()
4139
4195
  @click.argument("sso-client-vault-secret-path", required=True)
4140
4196
  @click.pass_context
4141
- def remove(ctx, sso_client_vault_secret_path: str):
4197
+ def remove(ctx: click.Context, sso_client_vault_secret_path: str) -> None:
4142
4198
  """Remove an existing SSO client"""
4143
4199
  vault_settings = get_app_interface_vault_settings()
4144
4200
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
@@ -4185,8 +4241,12 @@ def remove(ctx, sso_client_vault_secret_path: str):
4185
4241
  )
4186
4242
  @click.pass_context
4187
4243
  def external_resources(
4188
- ctx, provision_provider: str, provisioner: str, provider: str, identifier: str
4189
- ):
4244
+ ctx: click.Context,
4245
+ provision_provider: str,
4246
+ provisioner: str,
4247
+ provider: str,
4248
+ identifier: str,
4249
+ ) -> None:
4190
4250
  """External resources commands"""
4191
4251
  ctx.obj["provision_provider"] = provision_provider
4192
4252
  ctx.obj["provisioner"] = provisioner
@@ -4198,7 +4258,7 @@ def external_resources(
4198
4258
 
4199
4259
  @external_resources.command()
4200
4260
  @click.pass_context
4201
- def get_input(ctx):
4261
+ def get_input(ctx: click.Context) -> None:
4202
4262
  """Gets the input data for an external resource asset. Input data is what is used
4203
4263
  in the Reconciliation Job to manage the resource."""
4204
4264
  erv2cli = Erv2Cli(
@@ -4213,7 +4273,7 @@ def get_input(ctx):
4213
4273
 
4214
4274
  @external_resources.command()
4215
4275
  @click.pass_context
4216
- def request_reconciliation(ctx):
4276
+ def request_reconciliation(ctx: click.Context) -> None:
4217
4277
  """Marks a resource as it needs to get reconciled. The itegration will reconcile the resource at
4218
4278
  its next iteration."""
4219
4279
  erv2cli = Erv2Cli(
@@ -4240,7 +4300,7 @@ def request_reconciliation(ctx):
4240
4300
  default=False,
4241
4301
  )
4242
4302
  @click.pass_context
4243
- def migrate(ctx, dry_run: bool, skip_build: bool) -> None:
4303
+ def migrate(ctx: click.Context, dry_run: bool, skip_build: bool) -> None:
4244
4304
  """Migrate an existing external resource managed by terraform-resources to ERv2.
4245
4305
 
4246
4306
 
@@ -4346,7 +4406,7 @@ def migrate(ctx, dry_run: bool, skip_build: bool) -> None:
4346
4406
  @external_resources.command()
4347
4407
  @binary(["docker"])
4348
4408
  @click.pass_context
4349
- def debug_shell(ctx) -> None:
4409
+ def debug_shell(ctx: click.Context) -> None:
4350
4410
  """Enter an ERv2 debug shell to manually migrate resources."""
4351
4411
  # use a temporary directory in $HOME. The MacOS colima default configuration allows docker mounts from $HOME.
4352
4412
  with tempfile.TemporaryDirectory(dir=Path.home(), prefix="erv2-debug.") as _tempdir:
@@ -4385,7 +4445,7 @@ def debug_shell(ctx) -> None:
4385
4445
  prompt=True,
4386
4446
  )
4387
4447
  @click.pass_context
4388
- def force_unlock(ctx, lock_id: str) -> None:
4448
+ def force_unlock(ctx: click.Context, lock_id: str) -> None:
4389
4449
  """Manually unlock the ERv2 terraform state."""
4390
4450
  # use a temporary directory in $HOME. The MacOS colima default configuration allows docker mounts from $HOME.
4391
4451
  with tempfile.TemporaryDirectory(
@@ -4426,14 +4486,14 @@ def force_unlock(ctx, lock_id: str) -> None:
4426
4486
  @click.option("--include-pattern", help="Only include images that match this pattern")
4427
4487
  @click.pass_context
4428
4488
  def container_images(
4429
- ctx,
4430
- cluster_name,
4431
- namespace_name,
4432
- thread_pool_size,
4433
- use_jump_host,
4434
- exclude_pattern,
4435
- include_pattern,
4436
- ):
4489
+ ctx: click.Context,
4490
+ cluster_name: str,
4491
+ namespace_name: str,
4492
+ thread_pool_size: int,
4493
+ use_jump_host: bool,
4494
+ exclude_pattern: str,
4495
+ include_pattern: str,
4496
+ ) -> None:
4437
4497
  from tools.cli_commands.container_images_report import get_all_pods_images
4438
4498
 
4439
4499
  results = get_all_pods_images(
@@ -4480,7 +4540,7 @@ You can view the source of this Markdown to extract the JSON data.
4480
4540
  @get.command(help="Get all app tekton pipelines providers roles and users")
4481
4541
  @click.argument("app-name")
4482
4542
  @click.pass_context
4483
- def tekton_roles_and_users(ctx, app_name):
4543
+ def tekton_roles_and_users(ctx: click.Context, app_name: str) -> None:
4484
4544
  pp_namespaces = {
4485
4545
  p.namespace.path
4486
4546
  for p in get_tekton_pipeline_providers()
@@ -4507,6 +4567,7 @@ def tekton_roles_and_users(ctx, app_name):
4507
4567
  if not seen:
4508
4568
  seen = True
4509
4569
 
4570
+ users: str | list[str]
4510
4571
  if ctx.obj["options"]["output"] == "table":
4511
4572
  users = ", ".join([u.org_username for u in r.users])
4512
4573
  else:
@@ -4526,7 +4587,7 @@ def tekton_roles_and_users(ctx, app_name):
4526
4587
  )
4527
4588
  @click.argument("aws-account")
4528
4589
  @click.pass_context
4529
- def log_group_usage(ctx, aws_account):
4590
+ def log_group_usage(ctx: click.Context, aws_account: str) -> None:
4530
4591
  accounts = queries.get_aws_accounts(name=aws_account)
4531
4592
  if not accounts:
4532
4593
  print("no aws account found with that name")
@@ -4536,7 +4597,7 @@ def log_group_usage(ctx, aws_account):
4536
4597
  settings = queries.get_app_interface_settings()
4537
4598
  secret_reader = SecretReader(settings=settings)
4538
4599
  columns = ["log_group", "stored_bytes", "retention_days"]
4539
- results = []
4600
+ results: list[dict[str, str | int]] = []
4540
4601
 
4541
4602
  with AWSApi(1, [account], settings, secret_reader) as aws:
4542
4603
  session = aws.get_session(account["name"])